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」を宣言できますが、そのヘッダーは含まれません。

よろしく

/ロバート

4

10 に答える 10

12

できません。コンパイラは、クラスを宣言するときにオブジェクトのサイズを知る必要があります。

参照は代替手段ですが、構築時にインスタンス化する必要があるため、常に実行できるとは限りません。

別の代替手段はスマートポインターですが、技術的にはまだポインターだと思います。

ただし、ポインタを使用して他の構造を提案したくない理由を知っておくとよいでしょう...

于 2008-11-20T16:17:30.860 に答える
8

スマート ポインターを使用するだけです。この場合、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 の推奨事項については、ポインター データ メンバーのラップを参照してください。

デフォルトの構築を自動的に行いたい場合は、これを試してください:

#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;
}
于 2008-11-20T16:38:46.153 に答える
7

あなたが望むことはC++ではできません。オブジェクトのコードを生成するために、コンパイラはそのクラスが必要とするストレージの量を知る必要があります。それを知るためには、クラスの各メンバーに必要なストレージの量を知る必要があります。

foo 型のメンバを持つ bar 型のクラスを作成したい場合、コンパイラは foo の大きさを知る必要があります。それを知る唯一の方法は、foo の定義が (#include を介して) 利用可能であるかどうかです。それ以外の場合、唯一のオプションは、実際の foo オブジェクトの代わりに、foo の前方宣言とポインターまたは参照を使用することです。

于 2008-11-20T16:21:04.910 に答える
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;
};

コードは自明だと思います。ご不明な点がございましたら、お気軽にお問い合わせください。

于 2008-11-20T16:53:29.773 に答える
1

それを回避する方法はありません。

含める量を制限するのが最善の策ですが、クラス宣言を含むファイルを含める必要があります。クラス宣言を別のヘッダーに分割できますが、それには他に何も含まれていないことが期待されます。そうです、#include が必要ですが、インクルード階層をやや浅くしたままです。結局のところ、1 つのファイルを含めるのは安上がりです。階層が数百または数千のファイルに拡張された場合にのみ、問題が発生し始めます... ;)

于 2008-11-20T16:34:14.790 に答える
1

あなたができる唯一のことは、pImpl イディオムを使用して影響を最小限に抑えることです。これにより、foo.h をインクルードするときに、foo のインターフェースのみをインクルードすることになります。

foo.h を含めることは避けられませんが、できるだけ安くすることはできます。#inlcudes ではなく前方宣言を使用するというあなたの開発した習慣により、この道をうまく進んでいます。

于 2008-11-20T16:36:32.010 に答える
0

2 つのオブジェクトを関連付ける方法は、実際には 3 つしかありません。Bar に Foo を埋め込むか、または Foo をヒープに配置して Foo* を Bar に配置します。1 つ目は、クラス Bar を定義する前にクラス Foo を定義する必要があります。2 つ目は、クラス Foo を前方宣言するだけです。

3 番目のオプションは存在しますが、これは、質問で以前のオプションの両方を明確に除外したためです。(.cpp で) 静的 std::map を作成できます。すべての Bar コンストラクターで、このマップに Foo を追加し、キーをオンにしthisます。thisすべてのバーのメンバーは、マップを検索することで、関連する Foo を見つけることができます。Bar::~Bar はerase(this)、Foo を破棄するために呼び出します。

これにより sizeof(Bar) は変更されませんが、実際のメモリ使用量は Bar に Foo* を含めるよりも多くなります。ただし、バイナリ互換性が差し迫った懸念事項である場合は、これを行うこともできます。

于 2008-11-21T10:33:44.893 に答える
0

参照を使用できる場合は、同じ使用構文を保持できます。ただし、参照はコンストラクターですぐに初期化する必要があるため、ctor は絶対にアウトオブラインで定義する必要があります。(デストラクタでもオブジェクトを解放する必要があります。)

// bar.h
class foo;

class bar {
    foo& foo_;

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

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

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

あなたのマイレージは異なる場合があります。:-)

于 2008-11-20T16:16:47.243 に答える
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;
}
于 2008-11-20T16:49:59.907 に答える
0

pImpl イディオムを使用することもできます。

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

前方宣言の利点は引き続き得られますが、プライベートな実装を非表示にすることもできます。bar の実装を変更する場合、必ずしも bar.h を #include するすべてのソース ファイルをコンパイルする必要はありません。実装構造体自体は .cpp ファイルに含まれており、ここでオブジェクトを心ゆくまで宣言できます。

pImpl 自体が原因でパフォーマンスにわずかな影響がありますが、アプリケーションによっては、これは大した問題ではない場合があります。

私は大規模なプロジェクトに pImpl イディオムを使用しましたが、コンパイル時間に大きな違いがあります。残念なことに、言語は真にプライベートな実装を処理できませんが、これで完了です。

于 2008-11-20T18:13:36.180 に答える