2008-11-20 6 views
10

Ich verwende normalerweise, fast ohne zu denken, Vorwärtsdeklarationen, damit ich Header nicht einschließen muss. Etwas in diesem Beispiel:Alternative, um Deklarationen weiterzuleiten, wenn Sie #include nicht möchten

Einige Entwickler verwenden diese Methode gerne, um Probleme mit Einschlusskreisen zu vermeiden. Ich benutze es vielmehr, um den Overhead in umfangreichen Inklusionshierarchien zu minimieren, ein wichtiger Teil des physikalischen Designs (insbesondere für größere Projekte).

In einigen Fällen mag ich jedoch gerne Mitglieder als normale Objekte anstelle von Zeigern zu deklarieren, um vom automatischen Konstruktions-/Zerstörungsmechanismus zu profitieren. Dies führt zu dem Problem, dass nach vorn Erklärungen kann nicht mehr verwendet werden, da der Compiler die Klassendefinition in einem solchen Fall braucht, zB:

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

So würde ich mich freuen, wenn jemand eine alternative Sprachkonstrukt weiß, wo hier verwendet werden, so dass ich "foo_object" wie im Beispiel gezeigt deklarieren kann, aber ohne seinen Header einzuschließen.

Grüße

/Robert

Antwort

7

Verwenden Sie einfach einen intelligenten Zeiger - Sie können sogar auto_ptr in diesem Fall verwenden.

Sie erhalten alle Vorteile der automatischen Speicherverwaltung, ohne etwas über foo in bar.h wissen zu müssen. Siehe Wrapping Pointer Data Members für Herb Sutters Empfehlung.

Wenn Sie wirklich wollen Konstruktion standardmäßig automatisch geschehen, versuchen Sie dies:

#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

benötigt dies nicht immer noch die Initialisierung von foo_ptr im Konstruktor von bar? –

+0

tatsächlich, tut es. hehe –

+0

Und was, wenn Sie nicht automatische Zeiger benutzen können? – xan

0

Wenn Sie in der Lage sind, eine Referenz zu verwenden, können Sie die gleiche Syntax Verwendung beibehalten. Ihre Referenz muss jedoch direkt im Konstruktor initialisiert werden, so dass Ihr ctor absolut out-of-line definiert werden muss. (Sie werden auch zu dem Objekt in dem destructor befreien müssen.)

// bar.h 
class foo; 

class bar { 
    foo& foo_; 

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

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

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

Leistung kann variieren. :-)

12

Sie können nicht. Der Compiler muss die Größe des Objekts kennen, wenn er die Klasse deklariert.

Referenzen sind eine Alternative, obwohl sie zur Konstruktionszeit instanziiert werden müssen, so dass es nicht immer machbar ist.

Eine andere Alternative sind intelligente Zeiger, aber ich nehme an, das ist technisch immer noch ein Zeiger.

Es wäre gut, zu wissen, warum Sie nicht wenn ein Zeiger vorschlagen ein anderes Konstrukt verwenden möchten ...

+0

Technisch gesehen unterscheidet sich ein Member-Objekt nicht so sehr von einer Referenz, soweit Instanziierung bei der Konstruktion geht, also kein Verlust. :-) –

+0

Ein Element kann standardmäßig initialisiert und später (vollständig) über z. Setter. Sie können das nicht für ein Referenzmitglied tun. – Pieter

+0

Ich möchte den automatischen Konstruktions-/Zerstörungsmechanismus verwenden, daher ist eine Alternative, die im Konstruktor/Destruktor der Host-Klasse erforderliche Tasks impliziert, leider nicht ausreichend für mich. Vielen Dank. – sharkin

7

Was Sie wollen, kann nicht in C++ durchgeführt werden. Um Code für ein Objekt zu generieren, muss Ihr Compiler wissen, wie viel Speicher seine Klasse benötigt. Um das zu wissen, muss es wissen, wie viel Speicherplatz für jedes Mitglied der Klasse benötigt wird.

Wenn Sie eine Klasse von Typ bar mit einem Mitglied des Typs foo erstellen möchten, muss der Compiler wissen, wie groß ein foo ist. Der einzige Weg, den es kennt, ist, wenn es die Definition von foo verfügbar hat (über #include). Andernfalls besteht die einzige Möglichkeit darin, eine Forward-Deklaration von foo und einen Zeiger oder eine Referenz anstelle eines tatsächlichen foo-Objekts zu verwenden.

1

Es gibt keinen Weg dahin.

Ihre beste Wette ist, zu begrenzen, wie viel enthalten ist, aber Sie müssen die Datei mit der Klassendeklaration einschließen. Sie könnten die Deklaration der Klasse in eine separate Kopfzeile aufteilen, die hoffentlich nichts anderes enthält. Dann ja, du musst ein # include haben, aber du hältst deine Include-Hierarchie immer noch etwas flach. Immerhin, einschließlich einer Datei ist billig, ist es nur, wenn die Hierarchie auf Hunderte oder Tausende von Dateien erstreckt, dass es beginnt zu verletzen ...;)

1

So ziemlich alles, was Sie tun können, ist die Auswirkung von using the pImpl idiom zu minimieren, so dass, wenn Sie foo.h einschließen, Sie nur foo's Schnittstelle einschließen.

Sie können nicht vermeiden, einschließlich foo.h, aber Sie können es so billig wie möglich zu machen. Die Gewohnheit, die Sie aus der Verwendung von Voranmeldungen anstelle von #inlcudes entwickelt haben, hat Sie auf diesem Weg gut im Griff.

0

Sie könnten eine eigene „Smart Pointer“ Klasse verwenden, die automatisch erzeugt und zerstört eine Instanz. Dies würde die automatische Konstruktion und Zerstörung, die Sie anstreben, erreichen.

Um die Notwendigkeit eines anderen #include zu verhindern, können Sie diese myAuto Klasse in den Präfix-Header für Ihr Projekt einfügen, oder Sie können es kopieren und in jede Kopfzeile einfügen (keine gute Idee, aber es würde funktionieren).

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

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

Hier ist, wie Sie es verwenden würde:

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

hah, du hast die gleiche Idee wie ich: p –

+0

Hey - gute Idee :) –

2

Wie andere schon berichtet Sie es aus Gründen nicht tun können sie auch gesagt :) Du hast gesagt, dann wollen Sie nicht sich um die Konstruktion/Zerstörung der Mitglieder in der Klasse kümmern, die sie enthält. Sie können dafür Vorlagen verwenden.

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

Ich denke, der Code ist selbsterklärend. Wenn irgendwelche Fragen auftauchen, bitte mich ärgern.

0

Sie könnten auch die Pimpl Idiom verwenden, z.B .:

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

Sie immer noch die Vorteile der Vorwärts Erklärungen erhalten, plus Sie verstecken Ihre private Implementierung. Änderungen an der Implementierung von bar müssen nicht unbedingt alle Quelldateien, die #include bar.h enthalten, kompilieren. Die Implementierungsstruktur selbst ist in der CPP-Datei enthalten und hier können Sie Objekte nach Herzenslust deklarieren.

Sie haben einen kleinen Leistungseinbruch wegen pImpl selbst, aber abhängig von der Anwendung ist dies möglicherweise keine große Sache.

Ich habe das pImpl-Idiom für große Projekte verwendet und es macht einen großen Unterschied, Zeiten zu kompilieren. Schade, die Sprache kann nicht mit einer wirklich privaten Implementierung umgehen, aber da haben Sie es.

0

Es gibt wirklich nur drei Alternativen, um zwei Objekte zu verbinden. Du hast bereits zwei entdeckt: foo in Bar einbetten, oder Foo auf den Haufen legen und einen Foo * in Bar setzen. Die erste erfordert das Definieren der Klasse Foo vor dem Definieren der Klasse Bar; Die zweite erfordert lediglich, dass Sie die Deklarationsklasse Foo weiterleiten.

Eine dritte Option existiert, die ich nur erwähne, weil Sie beide vorherigen Optionen in Ihrer Frage ausdrücklich ausschließen. Sie können (in Ihrer .cpp) eine statische std :: map erstellen. In jedem Bar-Konstruktor fügen Sie dieser Map einen Foo hinzu, der unter this eingegeben wurde. Jedes Bar-Mitglied kann dann das zugehörige Foo finden, indem es in der Karte this nachschlägt. Bar :: ~ Bar ruft erase(this) an, um den Foo zu zerstören.

Während dies die Größe von (Bar) unverändert hält, ist die tatsächliche Speicherbelegung höher als die Einfügung eines Foo * in Bar. Sie können dies dennoch tun, wenn die binäre Kompatibilität ein dringendes Problem ist.

Verwandte Themen