Икона pimpl добавляет в ваш класс пустой элемент приватных данных, и это полезный метод, если вам нужно что-то быстрое и грязное. Однако у него есть свои недостатки. Главным среди них является то, что это затрудняет использование полиморфизма для абстрактного типа. Иногда вам может понадобиться абстрактный базовый класс и подклассы этого базового класса, собирать указатели на все различные типы в векторе и вызывать методы для них. Кроме того, если идиома pimpl состоит в том, чтобы скрыть детали реализации класса, то это только почти успешно: сам указатель является деталью реализации. Возможно, непрозрачная деталь реализации. Но деталь реализации тем не менее.
Существует альтернатива идиоме pimpl, которую можно использовать для удаления всех деталей реализации из интерфейса, предоставляя при этом базовый тип, который можно использовать полиморфно, если это необходимо.
В заголовочном файле вашей DLL (который # включен клиентским кодом) создайте абстрактный класс только с открытыми методами и концепциями, которые определяют, как должен создаваться экземпляр класса (например, общедоступные фабричные методы и методы клонирования):
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*
};
};
... Животное является абстрактным базовым классом и поэтому не может быть создано; никакой частный ctor не должен быть объявлен. Наличие виртуального dtor гарантирует, что, если кто-то delete
s Animal*
, будет также вызван соответствующий dtor подкласса.
Чтобы реализовать различные подклассы базового типа (например, dogs & cats), вы должны объявить классы уровня реализации в вашей 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. Вот пример драйвера:
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;
}