Domanda

Di solito, quasi senza pensarci più, uso le dichiarazioni in avanti in modo da non dover includere le intestazioni. Qualcosa lungo questo esempio:

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


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

class foo; // forward declaration

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

   foo* foo_pointer;
};

Ad alcuni sviluppatori piace usare questo metodo per evitare problemi con le cerchie di inclusione. Preferisco usarlo per ridurre al minimo il sovraccarico in ampie gerarchie di inclusione, una parte importante della progettazione fisica (in particolare per progetti più grandi).

Tuttavia, in alcuni casi mi piace davvero dichiarare i membri come oggetti normali anziché puntatori per beneficiare del meccanismo di costruzione / distruzione automatico. Ciò porta al problema che le dichiarazioni forward non possono più essere utilizzate, poiché in questo caso il compilatore necessita della definizione di classe, ad esempio:

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

Quindi, sarei felice se qualcuno conoscesse un costrutto di linguaggio alternativo che può essere usato qui in modo che io possa dichiarare " foo_object " come mostrato nell'esempio, ma senza includere l'intestazione.

Saluti

/ Robert

È stato utile?

Soluzione

Usa solo un puntatore intelligente: in questo caso puoi anche usare auto_ptr.

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

Ottieni tutti i vantaggi della gestione automatica della memoria, senza dover sapere nulla di pippo in bar.h. Vedi Avvolgimento dei membri dei dati del puntatore per la raccomandazione di Herb Sutter.

Se vuoi davvero che la costruzione di default avvenga automaticamente, prova questo:

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

Altri suggerimenti

Non puoi. Il compilatore deve conoscere la dimensione dell'oggetto quando dichiara la classe.

I riferimenti sono un'alternativa, anche se devono essere istanziati al momento della costruzione, quindi non è sempre possibile.

Un'altra alternativa sono i puntatori intelligenti, ma suppongo che tecnicamente sia ancora un puntatore.

Sarebbe bene sapere perché non vuoi usare un puntatore per suggerire qualche altro costrutto ...

Quello che vuoi non può essere fatto in C ++. Per generare codice per un oggetto, il compilatore deve sapere quanta memoria richiede la sua classe. Per saperlo, deve sapere quanta memoria è richiesta per ciascun membro della classe.

Se vuoi creare una classe di barra dei tipi con un membro di tipo foo, il compilatore deve sapere quanto è grande un foo. L'unico modo in cui lo sa è se ha la definizione di foo disponibile (tramite #include). Altrimenti, l'unica opzione è quella di utilizzare una dichiarazione in avanti di foo e un puntatore o riferimento invece di un vero oggetto foo.

Come altri hanno affermato che non puoi farlo per motivi che hanno dichiarato anche :) Hai quindi detto che non vuoi preoccuparti della costruzione / distruzione dei membri nella classe che li contiene. Puoi usare i template per questo.

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

Penso che il codice sia autoesplicativo. Se dovessero sorgere domande, per favore bug me.

Non c'è modo di evitarlo.

La tua scommessa migliore è limitare quanto è incluso, ma devi includere il file con la dichiarazione di classe. Potresti dividere la dichiarazione di classe in un'intestazione separata che, si spera, non includa nient'altro. Quindi sì, devi avere un #include, ma stai ancora mantenendo la tua gerarchia include un po 'superficiale. Dopotutto, incluso un file è economico, è solo quando la gerarchia si estende a centinaia o migliaia di file che inizia a danneggiare ...;)

Praticamente l'unica cosa che puoi fare è minimizzare l'impatto di usando il linguaggio pImpl in modo che quando includi foo.h includi solo l'interfaccia di foo.

Non puoi evitare di includere foo.h, ma puoi renderlo il più economico possibile. L'abitudine che hai sviluppato di usare dichiarazioni false piuttosto che #inlcudes ti ha portato bene su questo percorso.

Se sei in grado di utilizzare un riferimento, puoi conservare la stessa sintassi d'uso. Tuttavia, il tuo riferimento deve essere inizializzato immediatamente nel costruttore, quindi il tuo ctor deve assolutamente essere definito fuori linea. (Dovrai anche liberare l'oggetto nel distruttore.)

// bar.h
class foo;

class bar {
    foo& foo_;

public:
    bar();
    ~bar();
};

// bar.cc
bar::bar() : foo_(*new foo)
{
    // ...
}

bar::~bar()
{
    // ...
    delete &foo_;
}

Il tuo chilometraggio può variare. : -)

Puoi utilizzare un "puntatore intelligente" personalizzato " classe che crea e distrugge automaticamente un'istanza. Ciò favorirebbe la costruzione automatica e la distruzione che stai cercando.

Per evitare la necessità di un altro #include, puoi includere questa classe myAuto nell'intestazione del prefisso per il tuo progetto, oppure puoi copiarla e incollarla in ogni intestazione (non una buona idea, ma funzionerebbe).

template<class T>
class myAuto
{
    private:
        T * obj;

    public:
        myAuto() : obj(new T) {  }
        ~myAuto() { delete obj; }
        T& object() { return *obj; }
        T* operator ->() { return obj; }
};

Ecco come lo useresti:

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

Puoi anche usare il linguaggio pImpl, ad es .:

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

Ottieni ancora i vantaggi delle dichiarazioni a termine, oltre a nascondere la tua implementazione privata. Le modifiche all'implementazione di bar non richiedono necessariamente la compilazione di tutti i file di origine che #include bar.h. La stessa struttura di implementazione è autonoma nel file .cpp e qui puoi dichiarare gli oggetti nel tuo cuore.

Hai un piccolo calo delle prestazioni a causa di pImpl stesso, ma a seconda dell'applicazione potrebbe non essere un grosso problema.

Ho usato il linguaggio pImpl per progetti di grandi dimensioni e fa una grande differenza per i tempi di compilazione. Peccato che il linguaggio non sia in grado di gestire un'implementazione veramente privata, ma il gioco è fatto.

Esistono in realtà solo tre alternative per associare due oggetti. Ne hai già scoperti due: incorpora Foo in Bar o metti Foo sul mucchio e metti un Foo * in Bar. Il primo richiede la definizione della classe Foo prima di definire la classe Bar; il secondo richiede semplicemente di inoltrare la dichiarazione Foo di classe.

Esiste una terza opzione, che cito solo perché si escludono specificamente entrambe le opzioni precedenti nella domanda. Puoi (nel tuo .cpp) creare una std :: map statica. In ogni costruttore di barre aggiungi un Foo a questa mappa, digitato su this . Ogni membro della barra può quindi trovare il Foo associato cercando this nella mappa. Bar :: ~ Bar chiamerà cancella (questo) per distruggere il Foo.

Sebbene ciò mantenga invariata la dimensione di (Bar), l'utilizzo effettivo della memoria è superiore rispetto all'inclusione di un Foo * in Bar. Potresti comunque farlo se la compatibilità binaria è una preoccupazione urgente, tuttavia.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top