雑居房
基底クラスのコンテナに派生クラスをつっこみたい。
以下のソースで
#include <iostream> #include <map> #include <string> class Animal { public: Animal(void) { } virtual void Sound(void) const { std::cout << "★△■!?\n"; } }; class Cat : public Animal { public: Cat(void) { } void Sound(void) const { std::cout << "にゃあ\n"; } }; class Dog : public Animal { public: Dog(void) { } void Sound(void) const { std::cout << "わん\n"; } }; typedef std::map<std::string, Animal> TAnimalMap; int main(int argc, char *argv[]) { TAnimalMap AnimalMap; Cat mike; Cat tama; Dog pochi; Dog hachi; AnimalMap["mike"] = mike; AnimalMap["tama"] = tama; AnimalMap["pochi"] = pochi; AnimalMap["hachi"] = hachi; TAnimalMap::const_iterator it; for (it = AnimalMap.begin(); it != AnimalMap.end(); ++it) { std::cout << it->first << ": "; it->second.Sound(); } return 0; }
期待する出力は
hachi: わん mike: にゃあ pochi: わん tama: にゃあ
なのであるが、上のソースはダメな例で、出力は
hachi: ★△■!? mike: ★△■!? pochi: ★△■!? tama: ★△■!?
になる。コンテナにつっこんだりソートしたりするときに基底クラスとしてコピーされて格納されるため。
コンテナを基底クラスのポインタにする手もあるのだが、そうすると要素を削除する時にdeleteが必要になってしまうので、boost::shared_ptrを使ってみる。
#include <iostream> #include <map> #include <string> #include <boost/shared_ptr.hpp> class Animal { public: Animal(void) { } virtual void Sound(void) const { std::cout << "★△■!?\n"; } }; class Cat : public Animal { public: Cat(void) { } void Sound(void) const { std::cout << "にゃあ\n"; } }; class Dog : public Animal { public: Dog(void) { } void Sound(void) const { std::cout << "わん\n"; } }; typedef std::map< std::string, boost::shared_ptr<Animal> > TAnimalMap; int main(int argc, char *argv[]) { boost::shared_ptr<Cat> mike(new Cat()); boost::shared_ptr<Cat> tama(new Cat()); boost::shared_ptr<Dog> pochi(new Dog()); boost::shared_ptr<Dog> hachi(new Dog()); TAnimalMap AnimalMap; AnimalMap["mike"] = mike; AnimalMap["tama"] = tama; AnimalMap["pochi"] = pochi; AnimalMap["hachi"] = hachi; TAnimalMap::const_iterator it; for (it = AnimalMap.begin(); it != AnimalMap.end(); ++it) { std::cout << it->first << ": "; it->second.get()->Sound(); } return 0; }
これはOKなのだがなんか釈然としない。
基底クラスにデグレードしたときでもvtblが維持されればいいのかな、ということで自前で持たせてみる。
#include <iostream> #include <map> #include <string> class Animal { public: Animal(void) { SetSound(&Animal::_Sound); } typedef void (Animal::*TSound)(void) const; void SetSound(TSound sound) { m_Sound = sound; } void Sound(void) const { (this->*m_Sound)(); } private: TSound m_Sound; // オレオレvtbl void _Sound(void) const { std::cout << "★△■!?\n"; } }; class Cat : public Animal { public: Cat(void) { SetSound(static_cast<TSound>(&Cat::_Sound)); } private: void _Sound(void) const { std::cout << "にゃあ\n"; } }; class Dog : public Animal { public: Dog(void) { SetSound(static_cast<TSound>(&Dog::_Sound)); } private: void _Sound(void) const { std::cout << "わん\n"; } }; typedef std::map<std::string, Animal> TAnimalMap; int main(int argc, char *argv[]) { TAnimalMap AnimalMap; Cat mike; Cat tama; Dog pochi; Dog hachi; AnimalMap["mike"] = mike; AnimalMap["tama"] = tama; AnimalMap["pochi"] = pochi; AnimalMap["hachi"] = hachi; TAnimalMap::const_iterator it; for (it = AnimalMap.begin(); it != AnimalMap.end(); ++it) { std::cout << it->first << ": "; it->second.Sound(); } return 0; }
これでもいけた。デフォルトコピーコンストラクタがオレオレvtblをコピーしてくれるようだ。
拡張して、任意の関数を突っ込めて、存在したら呼び出せるという風にしてみた。
#include <iostream> #include <map> #include <string> class Animal { public: Animal(void) { } typedef void (Animal::*TFunction)(void) const; void SetFunction(const char *name, TFunction function) { m_FunctionMap[name] = function; } void Invoke(const char *function) const; private: typedef std::map<std::string, TFunction> TFunctionMap; TFunctionMap m_FunctionMap; }; void Animal::Invoke(const char *function) const { TFunctionMap::const_iterator it = m_FunctionMap.find(function); if (it != m_FunctionMap.end()) { (this->*(it->second))(); } } class Cat : public Animal { public: Cat(void) { SetFunction("sound", static_cast<TFunction>(&Cat::Sound)); } private: void Sound(void) const { std::cout << "にゃあ\n"; } }; class Dog : public Animal { public: Dog(void) { SetFunction("sound", static_cast<TFunction>(&Dog::Sound)); } private: void Sound(void) const { std::cout << "わん\n"; } }; typedef std::map<std::string, Animal> TAnimalMap; int main(int argc, char *argv[]) { TAnimalMap AnimalMap; Cat mike; Cat tama; Dog pochi; Dog hachi; AnimalMap["mike"] = mike; AnimalMap["tama"] = tama; AnimalMap["pochi"] = pochi; AnimalMap["hachi"] = hachi; TAnimalMap::const_iterator it; for (it = AnimalMap.begin(); it != AnimalMap.end(); ++it) { std::cout << it->first << ": "; it->second.Invoke("sound"); } return 0; }
こういうのはリフレクションっていうのかな。よくわからんが。
プロトコル定義がファイルで送られてくるようなものとか、オペレーションの権限を動的に変更するものとかに使えそう。