2008-11-20 89 views
10

我一般,幾乎不用思考了,使用前向聲明,這樣我就不必包含頭文件。沿着這個例子:替代轉發聲明,當你不想#包括

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


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

class foo; // forward declaration 

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

    foo* foo_pointer; 
}; 

一些開發人員喜歡用這種方法來避免包含問題。我寧願用它來最大限度地減少廣泛包含層次結構中的開銷,這是物理設計的一個重要部分(特別是對於大型項目)。

然而,在某些情況下,我真的很喜歡來聲明成員爲正常對象的指針,而不是從自動構造/銷燬機制中獲益。這導致前聲明不能再使用的問題,因爲編譯器需要在這種情況下,類定義,如:

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

所以,我會很高興,如果有人知道另一種語言結構,從而可以在這裏使用,這樣我可以聲明「foo_object」,如示例中所示,但不包括其頭部。

問候

/羅伯特

回答

7

只需使用智能指針 - 您甚至可以在這種情況下使用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; 
}; 

你得到自動內存管理的所有好處,而不必知道關於bar.h. FOO什麼對於Herb Sutter的推薦見Wrapping Pointer Data Members

如果你真的想默認構造自動發生,試試這個:

#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

如果你能夠使用一個參考,你可以保持相同的語法使用。然而,你的引用必須在構造函數中立即初始化,所以你的ctor必須被絕對定義。 (您還需要在析構函數中釋放對象。)

// bar.h 
class foo; 

class bar { 
    foo& foo_; 

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

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

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

您的里程可能會有所不同。 :-)

12

你不能。編譯器在聲明類時需要知道對象的大小。

引用是一種選擇,儘管他們在施工時被實例化,所以它並不總是可行的。

另一種選擇是智能指針,但我想這是在技術上還是一個指針。

這將是一件好事知道爲什麼你不希望使用指針,雖然提出了一些其他結構...

+0

從技術上講,成員對象與引用沒有太大的區別,只要在施工中實例化,所以在那裏沒有任何損失。 :-) – 2008-11-20 16:22:59

+0

成員可以被默認初始化,並在稍後通過例如(完全)設置。 setter方法。你不能爲參考成員做到這一點。 – Pieter 2008-11-20 16:28:54

+0

我想利用自動構建/銷燬機制,因此任何暗示宿主類的構造/析構函數中所需任務的替代方案都不足以滿足我的需求。謝謝。 – sharkin 2008-11-20 16:30:42

7

你想要什麼不能在C++完成。爲了生成一個對象的代碼,你的編譯器需要知道它的類需要多少存儲空間。爲了知道這一點,它必須知道每個班級成員需要多少存儲空間。

如果你想創建一個類類型巴和Foo類型的成員,編譯器必須知道FOO有多大。它知道的唯一方法就是它具有foo的可用定義(通過#include)。否則,唯一的選擇是使用foo的前向聲明和指針或引用,而不是實際的foo對象。

1

這是沒有辦法的。

最好的辦法是限制多少是包括在內,但你必須包括與類的聲明文件。你可以將類聲明分離成一個單獨的頭文件,希望它不包含其他內容。然後是的,你必須有一個#include,但你仍然保持你的包含層次結構有點淺。畢竟,包括一個文件是便宜的,只有當層次擴展到數百或數千個文件時,它纔開始傷害...;)

1

你可以做的唯一事情就是儘量減少using the pImpl idiom的影響,這樣當你包含foo.h時,你只能包含foo的接口。

你不能避免包括foo.h,但你可以儘可能便宜。你開發的使用前向聲明而不是#inlcudes的習慣在這條道路上很好。

0

你可以使用自定義的「智能指針」類自動創建和銷燬的實例。這會讓你實現自動建造和銷燬。

爲了防止另一個的#include的需要,您可以在前綴頭這myAuto類爲您的項目,或者您可以複製並粘貼到每頭(不是個好主意,但它的工作)。

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

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

這裏是你將如何使用它:

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

正如其他人說你不能對他們說太多的理由這樣做:)你接着說你不想關心包含它們的課程中的成員構建/破壞。你可以使用這個模板。

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

我認爲代碼是不言自明的。如果出現任何問題,請糾正。

0

你也可以使用平普爾成語,例如:

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

你仍然可以向前聲明的好處,再加上你隱藏你的私人執行。對實現欄的更改不一定需要編譯#include bar.h中的所有源文件。實現結構本身在.cpp文件中是獨立的,在這裏你可以聲明對象到你的心中。

由於pImpl本身,您的性能很小,但取決於應用程序,這可能不是什麼大問題。

我對大型項目使用了pImpl成語,它對編譯時間有很大的影響。可惜這門語言無法處理真正的私人實施,但你有它。

0

實際上只有三種選擇來關聯兩個對象。 你已經發現了兩個:在Foo中嵌入Foo,或者把Foo放在堆上,並將Foo *放在Bar中。第一個需要在定義類Bar之前定義Foo類;第二個只需要你轉發聲明類Foo。

第三個選項存在,我只提到,因爲你明確地排除了你的問題中的兩個選項。你可以(在你的.cpp中)創建一個靜態std :: map。在每個酒吧構造函數中,您都可以在此地圖上添加一個Foo,然後輸入this。然後,每位酒吧成員都可以通過在地圖中查找this來查找關聯的Foo。 Bar ::〜Bar會致電erase(this)摧毀Foo。

儘管這使sizeof(Bar)保持不變,但實際的內存使用量高於在Bar中包含Foo *。不過,如果二進制兼容性是一個緊迫的問題,你仍然可能會這樣做。