多重継承したクラスのポインタは、親クラスのポインタにキャストした場合、アドレスが同一にならない。
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でのサンプル
http://ideone.com/LdCNi
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"); } };
このように仮想関数を部分的にオーバーライドした場合、警告がでる。
回避するには、 class Subに、"using Base::hoge; "を加えるとよい。
本来なら同名のオーバーライドを避けるべきである。
http://stackoverflow.com/questions/2057823/issues-with-partial-class-function-overrides-in-c
#define hoge(x) HOGE(x)
などで、別名を無理やり定義されてしまっているのをすり抜ける方法。
(std::printf)("hoge") ; // printfが#defineされていてもオリジナルが呼ばれる
#define FOO printf FOO("hoge"); // printfが#defineされていてもオリジナルが呼ばれる
#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
#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; }
http://d.hatena.ne.jp/faith_and_brave/20091126/1259226643
// 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];
#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
// 参照型のインスタスをもつクラス 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
Hoge operator = (const Hoge& h) { memcpy(this, &h, sizeof(Hoge)); return *this; }
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の第二構文を使った初期化を使って実現する。 // 例外安全ではない。デストラクタ・コピーコンストラクタが例外を投げない // スレッドセーフではない。スレッドセーフにするには排他処理が必要。 Hoge& operator = (const Hoge& h) { if (this != &h) { // 自己代入でない this->~Hoge(); // thisを破棄 デストラクタは例外を投げない // ここで他のスレッドがthisを参照すると破綻する。シングルスレッド限定! try { new(this) Hoge(h); } catch (...) { assert(false); // 例外が投げられたら停止 } } return *this; } // リファレンスタイプのインスタンスは通常の方法ではswapできないので、 // コピーコンストラクタとnewの第二構文を使った代入の=を使って実現する。 // 例外安全ではない。コピーコンストラクタが例外を投げないことが前提 void swap(Hoge& h) { Hoge tmp(h); // hをコピー h = *this; *this = tmp; } bool operator < (const Hoge& h) const { return instance_ < h.instance_; } }; // output用 void print(const Hoge& h) { printf("%d\n", h.instance_); } } int main(int argc, char* argv[]) { int i1 = 1; int i2 = 2; int i3 = 3; Hoge h1(i1), h2(i2), h3(i3); std::vector<Hoge> vectorhoge; vectorhoge.push_back(h3); // operator =がないとvector::push_backはでき ない vectorhoge.push_back(h2); vectorhoge.push_back(h1); std::back_inserter(vectorhoge) = h1; std::sort(vectorhoge.begin(), vectorhoge.end()); // operator =がないと vector::sortはできない std::cout << "vector sort" << std::endl; std::for_each(vectorhoge.begin(), vectorhoge.end(), print); std::list<Hoge> listhoge; listhoge.push_back(h3); listhoge.push_back(h2); listhoge.push_back(h1); std::back_inserter(listhoge) = h1; listhoge.sort(); std::cout << "list sort" << std::endl; std::for_each(listhoge.begin(), listhoge.end(), print); return 0; }