雑居房

基底クラスのコンテナに派生クラスをつっこみたい。


以下のソースで

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

こういうのはリフレクションっていうのかな。よくわからんが。
プロトコル定義がファイルで送られてくるようなものとか、オペレーションの権限を動的に変更するものとかに使えそう。