2008-11-20 10 views
10

Normalmente, casi sin pensar más, uso las declaraciones de reenvío para no tener que incluir encabezados. Algo a lo largo de este ejemplo:Alternativa a las declaraciones de reenvío cuando no desea #include

//----------------------- 
// foo.h 
//----------------------- 
class foo 
{ 
    foo(); 
    ~foo(); 
}; 


//----------------------- 
// bar.h 
//----------------------- 

class foo; // forward declaration 

class bar 
{ 
    bar(); 
    ~bar(); 

    foo* foo_pointer; 
}; 

Algunos desarrolladores les gusta usar 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, realmente me gusta declarar a los miembros como objetos normales en lugar de indicadores para beneficiarse del mecanismo automático de construcción/destrucción. Esto nos lleva al problema de que las declaraciones prospectivas no se pueden utilizar más, ya que el compilador necesita la definición de clase en tal 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, yo sería feliz si alguien conoce una construcción del lenguaje alternativo que puede ser utilizado aquí para que pueda declarar "foo_object" como se muestra en el ejemplo, pero sin incluir su encabezado.

Saludos

/Robert

Respuesta

7

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. Ver Wrapping Pointer Data Members para la recomendación de Herb Sutter.

Si realmente quiere defecto de construcción a suceder de forma automática, 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; 
} 
+0

¿esto todavía no requiere la inicialización de foo_ptr en el constructor de la barra? –

+0

de hecho, lo hace. jeje –

+0

¿Y qué pasa si no puedes usar punteros automáticos? – xan

0

Si usted es capaz de utilizar una referencia, puede conservar la misma sintaxis 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 tendrá que 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 kilometraje puede variar. :-)

12

No puede. El compilador necesita saber el tamaño del objeto al declarar la clase.

Las referencias son una alternativa, aunque deben crearse instancias en tiempo de 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é usted no desea utilizar un puntero para sugerir alguna otra construcción, aunque ...

+0

Técnicamente, un objeto miembro no es tan diferente de una referencia en cuanto a creación de instancias en construcción, por lo que no hay pérdidas allí. :-) –

+0

Un miembro se puede inicializar de manera predeterminada y establecer de forma adecuada (completamente) más tarde, por ejemplo, Setters. No puedes hacer eso para un miembro de referencia. – Pieter

+0

Quiero utilizar el mecanismo automático de construcción/destrucción, por lo que desafortunadamente no es suficiente con cualquier alternativa que implique tareas obligatorias en el constructor/destructor de la clase de host. Gracias. – sharkin

7

Lo que se quiere 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 tipo barra con un miembro de tipo foo, el compilador debe saber qué tan grande es 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.

1

No hay forma de evitar eso.

Su mejor opción es limitar la cantidad que se incluye, pero debe incluir el archivo con la declaración de la 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 ...;)

1

Casi todo lo que se puede hacer es minimizar el impacto por using the pImpl idiom 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.

0

se puede utilizar una clase de "puntero inteligente", que crea y destruye una instancia de forma automática a medida. 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 la cabecera de prefijo para su proyecto, o puede copiar y pegar en cada cabecera (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; } 
};

Aquí es cómo usted lo utilizaría:

// 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; 
}
+0

ja, tienes la misma idea que yo: p –

+0

Hola, buena idea :) –

2

Como otros han dicho que no puede hacerlo por razones Indicaron también :) A continuación, ha dicho que no quiere preocuparse por la construcción/destrucción del miembro en la clase que los contiene. 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 se explica por sí mismo. Si surge alguna pregunta, búscame por favor.

0

También es posible usar el lenguaje 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; 
} 

Seguirá disfrutando de los beneficios de declaraciones adelantadas, además de que oculte su 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.

Tiene un pequeño impacto en el rendimiento debido a la acción, pero dependiendo de la aplicación, puede que no sea 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.

0

En realidad, hay solo tres alternativas para asociar dos objetos. Usted ya descubrió dos: incrustar 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 barra, agrega un Foo a este mapa, con la clave this. Cada miembro de la barra puede encontrar el Foo asociado al buscar this en el mapa. Bar :: ~ Bar llamará al 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.

Cuestiones relacionadas