pimpl idiom agrega un miembro de datos privados void * a su clase, y esta es una técnica útil si necesita algo rápido & sucio. Sin embargo, tiene sus inconvenientes. El principal de ellos es que dificulta el uso del polimorfismo en el tipo abstracto. A veces puede querer una clase base abstracta y subclases de esa clase base, recopilar punteros a todos los diferentes tipos en un vector y llamar a métodos sobre ellos. Además, si el propósito del idioma pimpl es ocultar los detalles de implementación de la clase, entonces solo tiene casi: el puntero en sí es un detalle de implementación. Un detalle de implementación opaco, tal vez. Pero un detalle de implementación, no obstante.
Una alternativa a la expresión idiomática pimpl existe que puede ser utilizado para eliminar todos los detalles de la implementación de la interfaz mientras que proporciona un tipo de base que se puede utilizar polimórficamente, si es necesario.
En el archivo de cabecera del archivo DLL (el #include por código de cliente) crear una clase abstracta con sólo métodos y conceptos públicas que dictan cómo la clase es crear una instancia (por ejemplo, métodos de fábrica públicas & métodos clon):
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*
};
};
... animales es un abstract base class y por lo tanto no puede ser instanciada; no es necesario declarar un ctor privado.La presencia del dtor virtual asegura que si alguien delete
es un Animal*
, también se llamará a la subclase adecuada 'dtor.
Para implementar diferentes subclases del tipo base (por ejemplo, perros & gatos), declararía las clases de nivel de implementación en su DLL. Estas clases derivan en última instancia de la clase base abstracta que usted declaró en su archivo de encabezado, y los métodos de fábrica en realidad crearían una instancia de una de estas subclases.
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";
}
Ahora usted tiene la interfaz definida en la cabecera & los detalles de implementación completamente separados de dónde código de cliente no puede ver nada en absoluto. Debería usar esto llamando a los métodos declarados en su archivo de encabezado desde el código que enlaza con su DLL. He aquí una muestra de controlador:
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;
}
Mejor deletreado 'pimpl' (para 'implementación privada')? –