c++ - Alternativa a enviar declaraciones cuando no desea#incluir
physical-design forward-declaration (10)
Como otros dijeron que no puedes hacerlo por las razones que dijeron también :) Luego dijiste que no querías preocuparte por la destrucción / destrucción del miembro en la clase que los contenía. Puedes usar plantillas para esto.
template<typename Type>
struct member {
boost::shared_ptr<Type> ptr;
member(): ptr(new Type) { }
};
struct foo;
struct bar {
bar();
~bar();
// automatic management for m
member<foo> m;
};
Creo que el código es auto explicativo. Si surge alguna pregunta, búscame por favor.
Por lo general, casi sin pensarlo, uso declaraciones directas para no tener que incluir encabezados. Algo a lo largo de este ejemplo:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo; // forward declaration
class bar
{
bar();
~bar();
foo* foo_pointer;
};
A algunos desarrolladores les gusta utilizar este método para evitar problemas con los círculos de inclusión. Prefiero utilizarlo para minimizar los gastos generales en las jerarquías de inclusión extensas, una parte importante del diseño físico (para proyectos más grandes en particular).
Sin embargo, en algunos casos me gusta declarar a los miembros como objetos normales en lugar de punteros para beneficiarse del mecanismo automático de construcción / destrucción. Esto lleva al problema de que las declaraciones avanzadas ya no pueden utilizarse, ya que el compilador necesita la definición de clase en dicho caso, por ejemplo:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo; // Not enough given the way we declare "foo_object"..
#include "foo.h" // ..instead this is required
class bar
{
bar();
~bar();
foo foo_object;
};
Por lo tanto, me gustaría que alguien conozca un constructo de lenguaje alternativo que se puede usar aquí para poder declarar "foo_object" como se muestra en el ejemplo, pero sin incluir su encabezado.
Saludos
/ Robert
Lo que quieres no se puede hacer en C ++. Para generar código para un objeto, su compilador necesita saber cuánto almacenamiento requiere su clase. Para saber eso, debe saber cuánto almacenamiento se requiere para cada miembro de la clase.
Si desea crear una clase de barra tipo con un miembro de tipo foo, el compilador debe saber qué tan grande es un foo. La única forma en que lo sabe es si tiene la definición de foo disponible (a través de #include). De lo contrario, su única opción es usar una declaración directa de foo y un puntero o referencia en lugar de un objeto foo real.
No hay forma de evitar eso.
Su mejor opción es limitar cuánto está incluido, pero debe incluir el archivo con la declaración de clase. Podría dividir la declaración de clase en un encabezado separado que, afortunadamente, no incluye nada más. Entonces sí, debes tener un #include, pero aún mantienes la jerarquía de inclusión algo superficial. Después de todo, incluir un archivo es barato, solo cuando la jerarquía se extiende a cientos o miles de archivos comienza a doler ...;)
No puedes. El compilador necesita saber el tamaño del objeto al declarar la clase.
Las referencias son una alternativa, aunque deben crearse instancias en el momento de la construcción, por lo que no siempre es posible.
Otra alternativa son los indicadores inteligentes, pero supongo que técnicamente sigue siendo un puntero.
Sería bueno saber por qué no quieres usar un puntero para sugerir alguna otra construcción, aunque ...
Podría usar una clase personalizada de "puntero inteligente" que crea y destruye automáticamente una instancia. Esto lograría la construcción automática y la destrucción que está buscando.
Para evitar la necesidad de otro #include, puede incluir esta clase myAuto
en el encabezado del prefijo para su proyecto, o puede copiarlo y pegarlo en cada encabezado (no es una buena idea, pero funcionaría).
template<class T>
class myAuto
{
private:
T * obj;
public:
myAuto() : obj(new T) { }
~myAuto() { delete obj; }
T& object() { return *obj; }
T* operator ->() { return obj; }
};
Así es como lo usarías:
// foo.h:
class foo
{
public:
foo();
~foo();
void some_foo_func();
};
//bar.h:
class foo;
class bar
{
public:
bar();
~bar();
myAuto<foo> foo_object;
};
//main.cc:
#include "foo.h"
#include "bar.h"
int main()
{
bar a_bar;
a_bar.foo_object->some_foo_func();
return 0;
}
Si puede usar una referencia, puede conservar la misma sintaxis de uso. Sin embargo, su referencia debe inicializarse de inmediato en el constructor, por lo que su código debe definirse claramente fuera de línea. (También necesitarás liberar el objeto en el destructor también).
// bar.h
class foo;
class bar {
foo& foo_;
public:
bar();
~bar();
};
// bar.cc
bar::bar() : foo_(*new foo)
{
// ...
}
bar::~bar()
{
// ...
delete &foo_;
}
Su experiencia puede ser diferente. :-)
Simplemente use un puntero inteligente; incluso puede usar auto_ptr en este caso.
//-----------------------
// bar.h
//-----------------------
#include <memory>
class foo; // Not enough given the way we declare "foo_object"..
class bar
{
public:
bar();
~bar();
foo &foo_object() { return *foo_ptr; }
const foo &foo_object() const { return *foo_ptr; }
private:
auto_ptr<foo> foo_ptr;
};
Obtiene todos los beneficios de la gestión automática de memoria, sin tener que saber nada sobre foo en bar.h. Consulte Cómo envolver los datos de los indicadores de puntero para obtener la recomendación de Herb Sutter.
Si realmente quiere que la construcción predeterminada ocurra automáticamente, intente esto:
#include <iostream>
using namespace std;
class Foo;
template <typename T>
class DefaultConstuctorPtr
{
T *ptr;
void operator =(const DefaultConstuctorPtr &);
DefaultConstuctorPtr(const DefaultConstuctorPtr &);
public:
DefaultConstuctorPtr() : ptr(new T()) {}
~DefaultConstuctorPtr() { delete ptr; }
T *operator *() { return ptr; }
const T *operator *() const { return ptr; }
};
class Bar
{
DefaultConstuctorPtr<Foo> foo_ptr;
public:
Bar() {} // The compiler should really need Foo() to be defined here?
};
class Foo
{
public:
Foo () { cout << "Constructing foo"; }
};
int main()
{
Bar bar;
}
También puede usar la expresión pImpl, por ejemplo:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo;
class bar
{
private:
struct impl;
boost::shared_ptr<impl> impl_;
public:
bar();
const foo& get_foo() const;
};
//-----------------------
// bar.cpp
//-----------------------
#include "bar.h"
#include "foo.h"
struct bar::impl
{
foo foo_object;
...
}
bar::bar() :
impl_(new impl)
{
}
const foo& bar::get_foo() const
{
return impl_->foo_object;
}
Aún obtienes los beneficios de las declaraciones anticipadas, además de ocultar tu implementación privada. Los cambios en la implementación de la barra no necesariamente requerirán compilar todos los archivos fuente que #include bar.h. La estructura de implementación en sí misma está contenida en el archivo .cpp y aquí puede declarar objetos al contenido de su corazón.
Usted tiene un pequeño golpe de rendimiento debido a la iniciativa en sí, pero dependiendo de la aplicación esto podría no ser un gran problema.
He usado la expresión pImpl para proyectos grandes y hace una gran diferencia en los tiempos de compilación. Es una pena que el lenguaje no pueda manejar una implementación verdaderamente privada, pero ahí lo tienes.
En realidad, solo hay tres alternativas para asociar dos objetos. Ya has descubierto dos: insertar Foo en Bar, o poner Foo en el montón y poner un Foo * en el Bar. El primero requiere definir la clase Foo antes de definir la clase Bar; el segundo simplemente requiere que reenvíes declarar la clase Foo.
Existe una tercera opción, que solo menciono porque excluye específicamente las dos opciones anteriores en su pregunta. Puedes (en tu .cpp) crear un std :: map estático. En cada constructor de Bar, agregas un Foo a este mapa, tecleado en this
. Cada miembro de la barra puede encontrar el Foo asociado al buscar this
en el mapa. Bar :: ~ Bar llamará erase(this)
para destruir el Foo.
Si bien esto mantiene sizeof (Bar) sin cambios, el uso de la memoria real es mayor que incluir un Foo * en la barra. Sin embargo, aún puede hacer esto si la compatibilidad binaria es una preocupación apremiante.
Prácticamente, lo único que puede hacer es minimizar el impacto utilizando el modismo de pImpl para que cuando incluya foo.h, solo incluya la interfaz de foo.
No puede evitar incluir foo.h, pero puede hacerlo lo más barato posible. El hábito que ha desarrollado de usar declaraciones avanzadas en lugar de #inlcudes lo lleva bien en este camino.