C++について、知っていそうで知らないこと、素朴な疑問など。(新着順)
定数の定義と静的構造体テーブルを同時に作るマクロ †定数の定義をソースコード上で行う場合、定数の定義と値の定義を同時に行いたい場合があります。 通常はこのように書きます。 enum EnumHoge { hoge, fuga, piyo }; struct HogeHoge { EnumHoge id; float value; const char* name; } const table[] = { { hoge, 1.0f, "hoge" }, { fuga, 2.0f, "fuga" }, { piyo, 3.0f, "piyo" }, }; enumの定義と構造体の定義が冗長で、名前の文字列の定義も冗長です。 // ここで定数を定義する(冗長でない単一の記載で済ませる) #define LIST \ DEF(hoge, 1.0f), \ DEF(fuga, 2.0f), \ DEF(piyo, 3.0f) // enumの生成 #undef DEF #define DEF(a, b) a enum EnumHoge { LIST }; // 構造体の生成 #undef DEF #define DEF(a, b) {a, b, #a} struct HogeHoge { EnumHoge id; float value; const char* name; } const table[] = { LIST }; 使用例 #include <stdio.h> int main(int ac, char*av []) { for (size_t n = 0; n < sizeof(table)/sizeof(HogeHoge); ++n) { printf("table[%d] = id:%d value:%f name='%s'\n", n, table[n].id, table[n].value, table[n].name); } } 結果 table[0] = id:0 value:1.000000 name='hoge' table[1] = id:1 value:2.000000 name='gufa' table[2] = id:2 value:3.000000 name='piyo' ideone: http://ideone.com/HEIsAa#view_edit_box コンテナに対して、pushとpush_backを自動判別して追加するUtility (C++11) †// decltypeを使ってコンテナに要素を追加する // push_back()を持っている場合 template <typename CT, typename VT> inline auto push_to_container(CT& container, VT&& val, int=0) -> decltype(container.push_back(val)) { return container.push_back(val); } // push()がある場合 template <typename CT, typename VT> inline auto push_to_container(CT& container, VT&& val) -> decltype(container.push(val)) { return container.push(val); } 使用例 std::vector<int> hoge; std::priority_queue<int> fuga; ... push_to_container(hoge, 1); // hoge.push_back(1)が呼ばれる push_to_container(fuga, 1); // fuga.push(1)が呼ばれる 参考にしたページ VC++でイテレーターを高速化する †VC++では、イテレーターの範囲チェックが行われており、安全な反面、若干遅いです。 #define _SECURE_SCL 0 ちなみに、以下の設定で範囲チェックが有効になります。 #define _SECURE_SCL_1 #define _HAS_ITERATOR_DEBUGGING 1 boost mpl map for_each覚え書き †boost::mplのvectorやmapをfor_eachするサンプル。 mapのkeyだけ回すmap_keysのようなフィルタの例も作ってみました。 #include <iostream> #include <vector> #include <string> #include <typeinfo> #include <boost/mpl/list.hpp> #include <boost/mpl/map.hpp> #include <boost/mpl/for_each.hpp> #include <boost/mpl/key_type.hpp> using namespace boost; using namespace std; struct Disp { template <class Type> void operator()(const Type&) const { cout << typeid(Type).name() << endl; } template <typename K, typename V> void operator()(const mpl::pair<K,V>&) const { cout << typeid(K).name() << "," << typeid(V).name() << endl; } } disp; int main() { typedef mpl::list<int, std::string, char, std::vector<int> > tl; typedef mpl::map<mpl::pair<int,char>, mpl::pair<int,int>, mpl::pair<char, char> > t2; cout << "mpl list test" << endl; mpl::for_each<tl>(disp); cout << "mpl map test" << endl; mpl::for_each<t2>(disp); cout << "mpl map key_type test" << endl; mpl::for_each<t2, mpl::lambda<mpl::key_type<t2, mpl::_1> > >(disp); cout << "finish" << endl; return 0; } cpp_akiraさんのページにあったサンプルにmapを足してみました。 enumをコンテナのように扱う †http://stackoverflow.com/questions/1390703/enumerate-over-an-enum-in-c http://ideone.com/J6ThVM †普通の配列をboost:arrayに化けさせる †配列をboost::arrayやstd::vectorに置き換えてしまえばよいのですが、既存のコードを修正するのも大変なので、 既存の配列をboost::arrayに見せかけるツールを作りました。 template <typename T, size_t N> inline boost::array<T,N>& to_array(T(&ar)[N]) { return *reinterpret_cast<boost::array<T,N>*>(&ar[0]); } template <typename T, size_t N> inline const boost::array<T,N>& to_array(const T(&ar)[N]) { return *reinterpret_cast<const boost::array<T,N>*>(&ar[0]); } to_array(<配列変数>); で、boost::array<型,サイズ> に変換します。(無理やりキャストしているだけ) これで、生の配列変数に対して、iteratorとか、size()とか、stl的な機能を使うことができます。 ideone: http://ideone.com/oi43Mo 静的な文字列リテラルは、同名のものは同じアドレスを示す †プログラム中に"hoge"のように記載する文字列リテラルは、リンク時に静的な領域にまとめられ、同一の文字列は一つにまとめられます。(ビルドオプションで設定) それを利用して、静的リテラルのアドレスをハッシュ値として扱うことができます。 #include <iostream> const char* s1 = "hoge"; const char* s2 = "hoge"; int main(int ac, char*av[]) { const char* s3 = "hoge"; std::cout << "s1 == s2 : " << (s1 == s2) << std::endl; std::cout << "s1 == s3 : " << (s1 == s3) << std::endl; std::cout << "s1 == 'hoge' : " << (s1 == "hoge") << std::endl; } output s1 == s2 : 1 s1 == s3 : 1 s1 == 'hoge' : 1 整数型配列の初期化 †クラスインスタンスの整数型配列をゼロで初期化するには、初期化リストに記載するだけでよい。 before class Hoge { int a[10]; hoge() { memset(&a[0], 0, sizeof(a)); } }: after class Hoge { int a[10]; hoge() : a() { } }; 多重継承したクラスをnewの配置構文で確保したときの落とし穴 †多重継承したクラスのポインタは、親クラスのポインタにキャストした場合、アドレスが同一にならない。 class C : public A, public B {...} C* c = new C; assert((A*)c == (B*)c); // assertで停止 ポインタの値で比較するときは注意が必要。 同じ理由で、newの配置構文でクラスインスタンスを確保した場合、親クラスのポインタで削除すると不具合が発生する場合がある。 class C : public A, public B {...} void* buffer = malloc(sizeof(C)); C* c = new(buffer) C; B* b = c; // B*に代入 b->~B(); // ~B()がvirtualならば、デストラクタはA,Cとも正常に呼び出される free(b); // bの値はbufferと異なるので死にます ↓ ideone.comでのサンプル == true で比較してはいけない理由 †if文で値をbooleanとして比較するとき、trueと==で比較すると、思わぬ落とし穴にはまることがある。 ※ C++ では、intとbool値と比較しても、左辺値に対して暗黙の型変換は行われない。 #include <stdio.h> int main(int ac, char* av[]) { int a = 2; if (a) printf("a is true\n"); else printf("a is false\n"); if (a == true) printf("a is true\n"); else printf("a is false\n"); if (a != false) printf("a is true\n"); else printf("a is false\n"); if (static_cast<bool>(a) == true) printf("a is true\n"); else printf("a is false\n"); return 0; } 結果 a is true a is false a is true a is true "a == true" で比較したときだけ、結果が異なる。 仮想関数の部分オーバーライドについて †class Base { public: virtual void hoge() = 0; virtual void hoge(int a) { printf("a=%d\n", a); } }; class Sub : public Base { public: virtual void hoge() { printf("hoge\n"); } }; このように仮想関数を部分的にオーバーライドした場合、警告がでる。 本来なら同名のオーバーライドを避けるべきである。 http://stackoverflow.com/questions/2057823/issues-with-partial-class-function-overrides-in-c #defineマクロとの戦い †#define hoge(x) HOGE(x) などで、別名を無理やり定義されてしまっているのをすり抜ける方法。
コード例 (http://ideone.com/S44K6) †#include <stdio.h> namespace foo { void hoge(const char* const msg) { printf("small hoge : %s\n", msg); } void HOGE(const char* const msg) { printf("LARGE HOGE : %s\n", msg); } } #define hoge(msg) HOGE(msg) #define FOO int main(int ac, char* av[]) { foo::hoge("ns::function"); (foo::hoge)("(ns::function)"); foo::hoge FOO("ns::function FOO"); }; output LARGE HOGE : ns::function small hoge : (ns::function) small hoge : ns::function FOO boost pool †
orderedとは †
通常版(非orederd) †
ordered、非orderedともに一長一短で使いにくい †オーバーライドした関数の戻り値は、基本クラスの定義の戻り値を継承したものでもOK †#include <stdio.h> struct Base { virtual Base* create() = 0; // create new instance virtual Base* clone() = 0; // create and copy instance virtual void print() = 0; }; struct Sub : Base { Sub() {} Sub(const Sub& sub) {} // copy constructor Sub* create() { return new Sub(); } Sub* clone() { return new Sub(*this); } void print() { printf("I am Sub\n"); } }; int main(int ac, char* av[]) { Base* base1 = Sub().create(); Base* base2 = base1->clone(); base2->print(); return 0; }
boost:multiindexは便利 †http://d.hatena.ne.jp/faith_and_brave/20091126/1259226643 virtualやポインタを使わず、サブクラスの生オブジェクトをコンテナに入れるためのクラス †// BaseClassのサブクラスをコンテナにいれるためのクラス // このクラスを使うことで、ポインタを使わず動的ポリモーフィズムを実現する template <typename BaseClass, size_t MaxSize> struct DynamicPolymorphism : BaseClass { uint8_t body[MaxSize - sizeof(BaseClass)]; DynamicPolymorphism() : BaseClass() {} DynamicPolymorphism(const DynamicPolymorphism& m) : BaseClass(m) { memcpy(body, m.body, sizeof(body)); } template <typename SubClass> DynamicPolymorphism(const SubClass& m) : BaseClass(m) { SubClass& self = reinterpret_cast<SubClass&>(*this); self = m; } template <typename SubClass> void operator = (const SubClass& m) { SubClass& self = reinterpret_cast<SubClass&>(*this); self = m; } template <typename SubClass> operator SubClass& () { return reinterpret_cast<SubClass&>(*this); } }; ※これでunionから開放されます 比較的サイズが小さいクラスを実体のままコンテナに格納する場合、継承したクラスオブジェクトを入れる事が難しい。手っ取り早い方法としては、unionを使う事があるが、unionを使うとコンストラクタが書けないなどのC++11以前の制約がある。 例 class Car { int cylinders; /* 気筒数*/ int displacement; /* 排気量 */ }; class Track : public Car { int payload = 1000; } // 最大積載量 class Bus : public Car { int maxPeoples = 30; } // 乗車定員 vector<Car> carList; carList.push_back(Truck()); // NG TruckがCarでスライシングされてpayloadが失われる carList.push_back(Bus()); // NG BusがCarでスライシングされてmaxPeoplesが失われる vector<Car*> carList2; carList2.push_back(new Truck()); // OK Truckの情報も保持される carList2.push_back(new Bus()); // OK maxPeoplesの情報も保持される ポインタを使えば解決するけど、小さいオブジェクトを全部newで作るのは効率が悪い。 unionを使った解決方法 class AllCar { enum Type { Truck, Bus }; Type type; int cylinders; int displacement; union { int payload; int maxPeoples; }; }; vector<AllCar> allCarList; AllCar truck; truck.type = AllCar::truck; allCarList.push_back(truck); DynamicPolymorphismを使った例 class Car { enum Type { Truck, Bus }; Type type; int cylinders; /* 気筒数*/ int displacement; /* 排気量 */ }; }; class Track : public Car { int payload = 1000; } // 最大積載量 class Bus : public Car { int maxPeoples = 30; } // 乗車定員 typedef DynamicPolymorphism<Car, max(sizeof(Truck), sizeof(Bus))> AllCars; vector<AllCars> allCars; allCars.push_back(Truck()); // AllCarsは継承クラスの最大サイズが確保されているのでスライシングは起こらない allCars.push_back(Bus()); Truck& t = allCars.front(); // DynamicPolymorphismのオペレーターでTruckに変換される Bus& t = allCars[1]; operator new の効力範囲 †#include <stdio.h> #include <stdlib.h> void* operator new (size_t sz) throw() { printf("global operator new\n"); return 0; } int main(int ac, char* av[]) { struct newtest { static void* operator new (size_t sz) throw() { printf("newtest operator new\n"); return 0; } void dotest() { char* a = new char; printf("a = %p\n", a); } }; newtest t; t.dotest(); newtest* t2 = new newtest; }; 実行結果 global operator new a = (nil) newtest operator new 参照型のメンバをもつクラスのコンテナとソート †参照型のインスタンスをもつクラスは、通常だとvector等のコンテナに格納できない。 †// 参照型のインスタスをもつクラス struct Hoge { int& instance_; Hoge(int& i) : instance_(i) {} Hoge(const Hoge& h) : instance_(h.instance_) {} bool operator < (const Hoge& h) const { return instance_ < h.instance_; } }; std::vectorに入れてみる std::vector<Hoge> vectorhoge; int h1 = 1; vectorhoge.push_back(h); // コンパイルエラー operator =がないとvector::push_backはできない listならOK std::list<Hoge> listhoge; int h1 = 1; listhoge.push_back(h); // OK vectorに格納可能なリファレンスをもつクラスの定義 †
禁断の技その1 †Hoge operator = (const Hoge& h) { memcpy(this, &h, sizeof(Hoge)); return *this; }
禁断の技その2 †Hoge operator = (const Hoge& h) { this->~Hoge(); new (this) Hoge(h); return *this; }
コードの例 †#include <cstdio> #include <list> #include <vector> #include <algorithm> #include <functional> #include <iostream> // 参照を持つクラスのswap namespace { struct Hoge { int& instance_; Hoge(int& i) : instance_(i) {} Hoge(const Hoge& h) : instance_(h.instance_) {} // コピーコンストラクタとnewの第二構文を使った初期化を使って実現する。 // 例外安全ではない。デストラクタ・コピーコンストラク |