19

コンストラクター内に他の子オブジェクトを作成するオブジェクトがあり、「this」を渡して、子がポインターを親に戻すことができるようにします。std :: auto_ptrまたはrawポインターのより安全な代替手段として、プログラミングでboost::shared_ptrを広範囲に使用しています。したがって、子には、などのコードがshared_ptr<Parent>あり、boostは親が子shared_from_this()に与えることができるメソッドを提供します。

私の問題はshared_from_this()、コンストラクターで使用できないことです。これは、実際には犯罪ではありません。何をしているのかを理解し、制限を気にしない限り、「this」をコンストラクターで使用するべきではないからです。

GoogleのC++スタイルガイドでは、コンストラクターはメンバー変数を初期値に設定するだけでよいと述べています。複雑な初期化は、明示的なInit()メソッドで行う必要があります。これにより、「コンストラクター内のこの」問題だけでなく、他のいくつかの問題も解決されます。

私を悩ませているのは、あなたのコードを使用している人々は、あなたのオブジェクトの1つを構築するたびにInit()を呼び出すことを忘れないでください。これを強制するために私が考えることができる唯一の方法は、Init()がすでにすべてのメンバー関数の先頭で呼び出されているというアサーションを持つことですが、これは書くのが面倒で実行が面倒です。

途中でこの問題を解決するイディオムはありますか?

4

6 に答える 6

16

ファクトリメソッドを使用してクラスを2フェーズで構築および初期化し、ctor&Init()関数をプライベートにします。そうすれば、オブジェクトを誤って作成する方法はありません。デストラクタを公開し、スマートポインタを使用することを忘れないでください。

#include <memory>

class BigObject
{
public:
    static std::tr1::shared_ptr<BigObject> Create(int someParam)
    {
        std::tr1::shared_ptr<BigObject> ret(new BigObject(someParam));
        ret->Init();
        return ret;
    }

private:
    bool Init()
    {
        // do something to init
        return true;
    }

    BigObject(int para)
    {
    }

    BigObject() {}

};


int main()
{
    std::tr1::shared_ptr<BigObject> obj = BigObject::Create(42);
    return 0;
}

編集:

スタック上に存在することに反対したい場合は、上記のパターンの変形を使用できます。書かれているように、これは一時的なものを作成し、コピーコンストラクタを使用します:

#include <memory>

class StackObject
{
public:
    StackObject(const StackObject& rhs)
        : n_(rhs.n_)
    {
    }

    static StackObject Create(int val)
    {
        StackObject ret(val);
        ret.Init();
        return ret;
    }
private:
    int n_;
    StackObject(int n = 0) : n_(n) {};
    bool Init() { return true; }
};

int main()
{
    StackObject sObj = StackObject::Create(42);
    return 0;
}
于 2010-03-24T18:53:20.193 に答える
9

GoogleのC++プログラミングガイドラインは、ここや他の場所で何度も批判されてきました。そして当然そうです。

2フェーズ初期化は、ラッピングクラスの背後に隠されている場合にのみ使用します。初期化関数を手動で呼び出すことが機能する場合でも、Cでプログラミングを行っており、コンストラクターを使用したC++は発明されていません。

于 2010-03-24T18:53:12.417 に答える
5

状況によっては、共有ポインタが何も追加しない場合があります。ライフタイム管理が問題になるときはいつでも使用する必要があります。子オブジェクトの存続期間が親の存続期間よりも短いことが保証されている場合、rawポインターの使用に問題はありません。たとえば、親が子オブジェクトを作成および削除する場合(そして他の誰も削除しない場合)、誰が子オブジェクトを削除する必要があるかについては疑問の余地がありません。

于 2010-03-24T19:21:13.623 に答える
3

KeithBには、私が拡張したい本当に良い点があります(質問とは関係ありませんが、コメントには収まりません):

オブジェクトとそのサブオブジェクトの関係の特定のケースでは、存続期間が保証されます。親オブジェクトは常に子オブジェクトよりも長持ちします。この場合、子(メンバー)オブジェクトは親(含む)オブジェクトの所有権を共有しないshared_ptrため、を使用しないでください。セマンティックな理由(共有所有権がまったくない)や実用的な理由で使用しないでください。メモリリークや誤った削除など、あらゆる種類の問題が発生する可能性があります。

説明を簡単にするためPに、親オブジェクトCを参照し、子オブジェクトまたは含まれているオブジェクトを参照するために使用します。

Pライフタイムが外部で。で処理される場合、参照するために別のライフタイムshared_ptrを追加すると、サイクルを作成する効果があります。参照カウントによって管理されるメモリのサイクルがあると、おそらくメモリリークが発生します。参照する最後の外部がスコープ外になっても、ポインタはまだ生きているため、の参照カウントは0に到達せず、オブジェクトアクセスできなくなっても、リリースされません。shared_ptrCPshared_ptrPCP

が別のポインタによって処理される場合P、ポインタが削除されると、デストラクタが呼び出され、Pデストラクタの呼び出しにカスケードされCます。の参照カウントはP0shared_ptrC達し、二重削除がトリガーされます。

自動ストレージ期間がある場合P、デストラクタが呼び出されると(オブジェクトがスコープ外になるか、含まれているオブジェクトデストラクタが呼び出されると)、shared_ptrは新しく作成されなかったメモリブロックの削除をトリガーします。

一般的な解決策は、sでサイクルを分割するweak_ptrことです。これにより、子オブジェクトはashared_ptrを親に保持するのではなく、を保持しweak_ptrます。この段階での問題は同じです。オブジェクトを作成するにはweak_ptr、オブジェクトを既に管理している必要がありますshared_ptr。これは、構築中には発生しません。

生のポインター(ポインターを介してリソースの所有権を処理することは安全ではありませんが、ここでは所有権は外部で処理されるため、問題にはなりません)または参照(参照されたオブジェクトPが長生きすることを信頼していることを他のプログラマーに伝えています)のいずれかを使用することを検討してください参照オブジェクトC

于 2010-03-24T20:01:49.253 に答える
0

複雑な構造を必要とするオブジェクトは、工場の仕事のように聞こえます。

インターフェースまたは抽象クラスを定義します。これは構築できません。さらに、パラメーターを使用してインターフェースへのポインターを返すが、舞台裏で複雑さを処理するフリー関数を定義します。

クラスのエンドユーザーが何をしなければならないかという観点からデザインを考える必要があります。

于 2010-03-24T19:00:18.570 に答える
0

この場合、本当にshared_ptrを使用する必要がありますか?子供はただポインターを持つことができますか?結局のところ、これは子オブジェクトなので、親が所有しているので、親への通常のポインターを持っているだけではないでしょうか。

于 2010-03-24T20:05:34.957 に答える