C++ Tips

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;
}
  1. thisのデストラクタが呼ばれないで破棄される
  2. オブジェクトのバイナリーコピーは非常に危険なのでダメ

禁断の技その2

Hoge operator = (const Hoge& h) {
  this->~Hoge();
  new (this) Hoge(h);
  return *this;
}
  1. いちおう、言語的には安全。
  2. ハーブ・サッターさんは絶対に使うなと警告している方法
  3. コピーコンストラクタとデストラクタで例外を投げないことが前提。例外が発生すると破綻する。
  4. スレッドセーフでもない。シングルスレッド限定。
  5. 自己代入の処理も必要
  6. 欠点は多いけど、これでvectorに対する要素の追加やsortが可能になる

コードの例

#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;
}

C++OPTIONAL


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS