pimpl习惯用法为你的类添加了一个 void* 私有数据成员,如果你需要一些快速和肮脏的东西,这是一种有用的技术。然而,它有它的缺点。其中主要是它使得在抽象类型上使用多态变得困难。有时您可能需要一个抽象基类和该基类的子类,收集指向向量中所有不同类型的指针并调用它们的方法。此外,如果 pimpl 习惯用法的目的是隐藏类的实现细节,那么它几乎成功了:指针本身就是一个实现细节。也许是一个不透明的实现细节。但仍然是一个实现细节。
存在 pimpl 习惯用法的替代方法,可用于从接口中删除所有实现细节,同时提供可在需要时多态使用的基本类型。
在您的 DLL 的头文件(客户端代码中的#included)中创建一个抽象类,其中只有公共方法和概念,这些方法和概念指示如何实例化该类(例如,公共工厂方法和克隆方法):
狗窝
/****************************************************************
***
*** 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*
};
};
...Animal 是一个抽象基类,因此无法实例化;不需要声明私有 ctor。虚拟 dtor 的存在确保如果有人delete
s an Animal*
,也会调用正确的子类的 dtor。
为了实现基类型的不同子类(例如狗和猫),您将在 DLL 中声明实现级类。这些类最终派生自您在头文件中声明的抽象基类,工厂方法实际上会实例化这些子类之一。
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 的代码调用头文件中声明的方法来使用它。这是一个示例驱动程序:
主.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;
}