- 追加された行はこの色です。
- 削除された行はこの色です。
[[C++ Tips]]
* virtualやポインタを使わず、サブクラスの生オブジェクトをコンテナに入れるためのクラス [#d948f293]
// 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 の効力範囲 [#tdb48b30]
#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
* 参照型のメンバをもつクラスのコンテナとソート [#j00f0661]
** 参照型のインスタンスをもつクラスは、通常だとvector等のコンテナに格納できない。 [#k54c94c3]
// 参照型のインスタスをもつクラス
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に格納可能なリファレンスをもつクラスの定義 [#vea9f3c7]
- vectorに格納(push_back, back_insert_iteratorなど)には、operator = が必要
- リファレンス型の変数は、リファレンス情報の更新はできない。代入もswapも参照先のデータにたいして行われる。
*** 禁断の技その1 [#m5f8dfc7]
Hoge operator = (const Hoge& h) {
memcpy(this, &h, sizeof(Hoge));
return *this;
}
+ thisのデストラクタが呼ばれないで破棄される
+ オブジェクトのバイナリーコピーは非常に危険なのでダメ
*** 禁断の技その2 [#f178b88a]
Hoge operator = (const Hoge& h) {
this->~Hoge();
new (this) Hoge(h);
return *this;
}
+ いちおう、言語的には安全。
+ ハーブ・サッターさんは絶対に使うなと警告している方法
+ コピーコンストラクタとデストラクタで例外を投げないことが前提。例外が発生すると破綻する。
+ スレッドセーフでもない。シングルスレッド限定。
+ 自己代入の処理も必要
+ 欠点は多いけど、これでvectorに対する要素の追加やsortが可能になる
*** コードの例 [#s7418473]
#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;
}