pimpl idiomは、あなたのクラスにvoid *プライベートデータメンバを追加します。これは素早いものが必要な場合に便利なテクニックです。&しかしそれには欠点があります。その中でも、抽象型に多型を使用することが難しくなっています。時には、抽象基本クラスとその基本クラスのサブクラスが必要な場合があり、ベクトル内のすべての異なる型へのポインタを収集し、それらのメソッドを呼び出します。 PIMPLイディオムの目的は、クラスの実装の詳細を非表示にする場合に加えて、それが唯一ほぼ成功:ポインタ自体は、実装の詳細です。おそらく不透明な実装の詳細です。それにもかかわらず、実装の詳細。
PIMPLイディオムに代わるものは、必要に応じて、多形を使用することができる基本型を提供しながら、インタフェースから実装の詳細の全てを除去するために使用することができる存在します。あなたのDLLのヘッダファイル(クライアントコードから#include 1)で
は、クラスがインスタンス化する方法を指示パブリックメソッドのみと概念(例えば、公共のファクトリメソッド&クローン法)で抽象クラスを作成します。
を
kennel.h
/****************************************************************
***
*** The declaration of the kennel namespace & its members
*** would typically be in a header file.
***/
// Provide an abstract interface class which clients will have pointers to.
// Do not permit client code to instantiate this class directly.
namespace kennel
{
class Animal
{
public:
// factory method
static Animal* createDog(); // factory method
static Animal* createCat(); // factory method
virtual Animal* clone() const = 0; // creates a duplicate object
virtual string speak() const = 0; // says something this animal might say
virtual unsigned long serialNumber() const = 0; // returns a bit of state data
virtual string name() const = 0; // retuyrns this animal's name
virtual string type() const = 0; // returns the type of animal this is
virtual ~Animal() {}; // ensures the correct subclass' dtor is called when deleteing an Animal*
};
};
...動物はabstract base classですので、インスタンス化することはできません。プライベートctorを宣言する必要はありません。仮想dtorの存在により、誰かdelete
がAnimal*
であれば、適切なサブクラスdtorも呼び出されます。
基本タイプの異なるサブクラス(例:dog & cat)を実装するには、DLLに実装レベルのクラスを宣言します。これらのクラスは最終的にヘッダファイルで宣言した抽象基本クラスから派生し、ファクトリメソッドは実際にこれらのサブクラスの1つをインスタンス化します。
dll.cpp:
/****************************************************************
***
*** The code that follows implements the interface
*** declared above, and would typically be in a cc
*** file.
***/
// Implementation of the Animal abstract interface
// this implementation includes several features
// found in real code:
// Each animal type has it's own properties/behavior (speak)
// Each instance has it's own member data (name)
// All Animals share some common properties/data (serial number)
//
namespace
{
// AnimalImpl provides properties & data that are shared by
// all Animals (serial number, clone)
class AnimalImpl : public kennel::Animal
{
public:
unsigned long serialNumber() const;
string type() const;
protected:
AnimalImpl();
AnimalImpl(const AnimalImpl& rhs);
virtual ~AnimalImpl();
private:
unsigned long serial_; // each Animal has its own serial number
static unsigned long lastSerial_; // this increments every time an AnimalImpl is created
};
class Dog : public AnimalImpl
{
public:
kennel::Animal* clone() const { Dog* copy = new Dog(*this); return copy;}
std::string speak() const { return "Woof!"; }
std::string name() const { return name_; }
Dog(const char* name) : name_(name) {};
virtual ~Dog() { cout << type() << " #" << serialNumber() << " is napping..." << endl; }
protected:
Dog(const Dog& rhs) : AnimalImpl(rhs), name_(rhs.name_) {};
private:
std::string name_;
};
class Cat : public AnimalImpl
{
public:
kennel::Animal* clone() const { Cat* copy = new Cat(*this); return copy;}
std::string speak() const { return "Meow!"; }
std::string name() const { return name_; }
Cat(const char* name) : name_(name) {};
virtual ~Cat() { cout << type() << " #" << serialNumber() << " escaped!" << endl; }
protected:
Cat(const Cat& rhs) : AnimalImpl(rhs), name_(rhs.name_) {};
private:
std::string name_;
};
};
unsigned long AnimalImpl::lastSerial_ = 0;
// Implementation of interface-level functions
// In this case, just the factory functions.
kennel::Animal* kennel::Animal::createDog()
{
static const char* name [] = {"Kita", "Duffy", "Fido", "Bowser", "Spot", "Snoopy", "Smkoky"};
static const size_t numNames = sizeof(name)/sizeof(name[0]);
size_t ix = rand()/(RAND_MAX/numNames);
Dog* ret = new Dog(name[ix]);
return ret;
}
kennel::Animal* kennel::Animal::createCat()
{
static const char* name [] = {"Murpyhy", "Jasmine", "Spike", "Heathcliff", "Jerry", "Garfield"};
static const size_t numNames = sizeof(name)/sizeof(name[0]);
size_t ix = rand()/(RAND_MAX/numNames);
Cat* ret = new Cat(name[ix]);
return ret;
}
// Implementation of base implementation class
AnimalImpl::AnimalImpl()
: serial_(++lastSerial_)
{
};
AnimalImpl::AnimalImpl(const AnimalImpl& rhs)
: serial_(rhs.serial_)
{
};
AnimalImpl::~AnimalImpl()
{
};
unsigned long AnimalImpl::serialNumber() const
{
return serial_;
}
string AnimalImpl::type() const
{
if(dynamic_cast<const Dog*>(this))
return "Dog";
if(dynamic_cast<const Cat*>(this))
return "Cat";
else
return "Alien";
}
は今、あなたはクライアントコードがすべてでそれを見ることができない実装の詳細が完全に区切らヘッダー&で定義されたインタフェースを持っています。 DLLにリンクするコードからヘッダーファイルで宣言されたメソッドを呼び出すことで、これを使用します。ここでは、サンプル・ドライバです:
main.cppに:
std::string dump(const kennel::Animal* animal)
{
stringstream ss;
ss << animal->type() << " #" << animal->serialNumber() << " says '" << animal->speak() << "'" << endl;
return ss.str();
}
template<class T> void del_ptr(T* p)
{
delete p;
}
int main()
{
srand((unsigned) time(0));
// start up a new farm
typedef vector<kennel::Animal*> Animals;
Animals farm;
// add 20 animals to the farm
for(size_t n = 0; n < 20; ++n)
{
bool makeDog = rand()/(RAND_MAX/2) != 0;
if(makeDog)
farm.push_back(kennel::Animal::createDog());
else
farm.push_back(kennel::Animal::createCat());
}
// list all the animals in the farm to the console
transform(farm.begin(), farm.end(), ostream_iterator<string>(cout, ""), dump);
// deallocate all the animals in the farm
for_each(farm.begin(), farm.end(), del_ptr<kennel::Animal>);
return 0;
}
より良いスペルの「ピンプル」(「プライベート実装」の場合)? –