lenguaje headers funcion estatica ejemplos cabecera archivos c++ file class header encapsulation

c++ - headers - funcion estatica ejemplos



¿Archivo de encabezado C++ que declara una clase y métodos pero no miembros? (8)

¿Es posible crear un archivo de encabezado C ++ (.h) que declare una clase y sus métodos públicos, pero no define los miembros privados en esa clase? Encontré algunas páginas que dicen que debes declarar la clase y todos sus miembros en el archivo de encabezado, luego defines los métodos por separado en tu archivo cpp. Lo pido porque quiero tener una clase que esté definida en una DLL Win32, y quiero que esté encapsulada correctamente: la implementación interna de esa clase podría cambiar, incluidos sus miembros, pero estos cambios no deberían afectar el código que usa la clase .

Supongo que si tuviera esto, sería imposible para el compilador conocer el tamaño de mis objetos antes de tiempo. Pero eso debería estar bien, siempre que el compilador sea lo suficientemente inteligente como para usar el constructor y simplemente pase los punteros a la ubicación en la memoria donde está almacenado mi objeto, y nunca me permita ejecutar "sizeof (MyClass)".

Actualización: ¡ Gracias a todos los que respondieron! Parece que el idioma pimpl es una buena forma de lograr lo que estaba hablando. Voy a hacer algo similar:

Mi archivo DLL Win32 tendrá un montón de funciones separadas como esta:

void * __stdcall DogCreate(); int __stdcall DogGetWeight(void * this); void __stdcall DogSetWeight(void * this, int weight);

Esta es la forma típica en que Microsoft escribe sus archivos DLL, por lo que creo que probablemente haya una buena razón para ello.

Pero quiero aprovechar la agradable sintaxis que C ++ tiene para las clases, así que escribiré una clase contenedora para resumir todas estas funciones. Tendrá un miembro, que será "vacid * pimpl". Esta clase de contenedor será tan simple que también podría simplemente declararlo Y definirlo en el archivo de encabezado. Pero esta clase de contenedor realmente no tiene otro objetivo que no sea hacer que el código de C ++ se vea bonito por lo que yo sé.


¿Es posible crear un archivo de encabezado C ++ (.h) que declare una clase y sus métodos públicos, pero no declare los miembros privados en esa clase?

La respuesta más cercana es el modismo PIMPL.

Refiérete a The Fast Pimpl Idiom de Herb Sutter.

IMim Pimpl es realmente útil durante las etapas iniciales de desarrollo donde su archivo de encabezado va a cambiar muchas veces. Pimpl tiene su costo debido a su asignación / desasignación de objeto interno en montón.


El idioma pimpl agrega un miembro de datos privados void * a su clase, y esta es una técnica útil si necesita algo rápido y 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 casi solo tiene éxito: 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.

Existe una alternativa a la expresión de pimpl que se puede utilizar para eliminar todos los detalles de implementación de la interfaz a la vez que proporciona un tipo de base que se puede utilizar polimórficamente, si es necesario.

En el archivo de encabezado de DLL (el #incluido por código de cliente), cree una clase abstracta con solo métodos y conceptos públicos que dictan cómo se instanciará la clase (por ejemplo, métodos de fábrica públicos y métodos de clonación):

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

... Animal es una clase base abstracta y, por lo tanto, no se puede crear una instancia; no es necesario declarar un ctor privado. La presencia del controlador virtual asegura que si alguien delete s un Animal* , también se llamará a la subclase adecuada.

Para implementar diferentes subclases del tipo base (por ejemplo, perros y gatos), declararías las clases de nivel de implementación en tu 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 tiene la interfaz definida en el encabezado y los detalles de implementación completamente separados, donde el código del cliente no puede verlo 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. Aquí hay un controlador de muestra:

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


Google "idioma de pimple" o "manejar C ++".



Sí, esto puede ser algo deseable de hacer. Una manera fácil es hacer que la clase de implementación se derive de la clase definida en el encabezado.

La desventaja es que el compilador no sabrá cómo construir su clase, por lo que necesitará algún tipo de método de fábrica para obtener instancias de la clase. Será imposible tener instancias locales en la pila.



Creo que lo que estás buscando es algo que se llama el "idioma pimpl". Para entender cómo funciona esto, debes entender que en C ++ puedes reenviar declarar algo así.

class CWidget; // Widget will exist sometime in the future CWidget* aWidget; // An address (integer) to something that // isn''t defined *yet* // later on define CWidget to be something concrete class CWidget { // methods and such };

Entonces reenviar declarar significa prometer declarar completamente un tipo más tarde. Se dice que "habrá algo llamado CWidget, lo prometo. Te contaré más sobre esto más adelante".

Las reglas de declaración directa dicen que puede definir un puntero o una referencia a algo que ha sido declarado adelante. Esto se debe a que los punteros y las referencias son en realidad direcciones simples, un número en el que lo que está por definirse será. Poder declarar un puntero a algo sin declararlo por completo es conveniente por muchas razones.

Es útil aquí porque puedes usar esto para ocultar algunos de los aspectos internos de una clase usando el método "pimpl". Pimpl significa "indicador de implementación". Entonces, en lugar de "widget" tienes una clase que es la implementación real. La clase que está declarando en su encabezado es solo un paso a través de la clase CImpl. Así es como funciona:

// Thing.h class CThing { public: // CThings methods and constructors... CThing(); void DoSomething(); int GetSomething(); ~CThing(); private: // CThing store''s a pointer to some implementation class to // be defined later class CImpl; // forward declaration to CImpl CImpl* m_pimpl; // pointer to my implementation };

Thing.cpp tiene los métodos de CThing definidos como pass-throughs a la impl:

// Fully define Impl class CThing::CImpl { private: // all variables public: // methods inlined CImpl() { // constructor } void DoSomething() { // actual code that does something } //etc for all methods }; // CThing methods are just pass-throughs CThing::CThing() : m_pimpl(new CThing::CImpl()); { } CThing::~CThing() { delete m_pimpl; } int CThing::GetSomething() { return m_pimpl->GetSomething(); } void CThing::DoSomething() { m_impl->DoSomething(); }

tada! Has ocultado todos los detalles en tu cpp y tu archivo de encabezado es una lista muy ordenada de métodos. Es una gran cosa. Lo único que puede ver diferente de la plantilla anterior es que las personas pueden usar boost :: shared_ptr <> u otro puntero inteligente para la impl. Algo que se elimina a sí mismo.

Además, tenga en cuenta que este método viene con algunas molestias. La depuración puede ser un poco molesto (nivel extra de redirección para avanzar). También es una gran cantidad de gastos generales para crear una clase. Si haces esto para cada clase, te cansarás de todo el tipeo :).


Debe declarar todos los miembros en el encabezado para que el compilador sepa qué tan grande es el objeto y así sucesivamente.

Pero puedes resolver esto usando una interfaz:

ext.h:

class ExtClass { public: virtual void func1(int xy) = 0; virtual int func2(XYClass &param) = 0; };

int.h:

class ExtClassImpl : public ExtClass { public: void func1(int xy); int func2(XYClass&param); };

int.cpp:

void ExtClassImpl::func1(int xy) { ... } int ExtClassImpl::func2(XYClass&param) { ... }