20

要件

  1. RCObject「参照カウントオブジェクト」を表す、というクラスを作成しています。
  2. クラスRCObjectは抽象であり、フレームワークの基本クラスとして機能する必要があります(EC ++ 3アイテム7)。
  3. RCObjectスタック上にサブクラスのインスタンスを作成することは禁止する必要があります( MEC ++ 1アイテム27)。

    [追加: ]

    [仮定Bearはの具体的なサブクラスですRCObject]

    [C.E.ここでコンパイルエラーを意味します]

    Bear b1;                        // Triggers C.E. (by using MEC++1 Item 27)
    Bear* b2;                       // Not allowed but no way to trigger C.E.
    intrusive_ptr<Bear> b3;         // Recommended
    
    Bear* bs1 = new Bear[8];                   // Triggers C.E.
    container< intrusive_ptr<RCObject> > bs2;  // Recommended
    intrusive_ptr_container<RCObject> bs3;     // Recommended
    
    class SomeClass {
    private:
        Bear m_b1;                 // Triggers C.E.
        Bear* m_b2;                // Not allowed but no way to trigger C.E.
        intrusive_ptr<Bear> m_b3;  // Recommended
    };
    
  4. 明確化:生のポインタRCObject(およびサブクラス)を宣言/返すことは禁止する必要があります(残念ながら、それを強制する実用的な方法はないと思います。つまり、ユーザーが従わない場合にコンパイルエラーをトリガーします)。上記の項目3のソースコードの例を参照してください。

  5. サブクラスのインスタンスは、JavaRCObjectの場合と同じようCloneableにクローン可能である必要があります。(MEC ++ 1アイテム25);
  6. サブクラス化するユーザーは、サブクラスの「ファクトリメソッド」RCObjectを記述できる必要があります。戻り値が無視された(変数に割り当てられていない)場合でも、メモリリークは発生しないはずです。これに近いメカニズムはObjective-Cにあります。autorelease

    [追加: cschwanKosRCObjectは、「smart-pointer-to- 」を返すだけで要件を満たすことができると指摘しました。]

  7. 明確化:サブクラスのインスタンスは、適切なコンテナーまたはコンテナーRCObjectに含めることができる必要があります。私は主に「-like」コンテナ、「-like 」コンテナ、「-like」コンテナが必要です。ベースラインはstd::boost::std::vectorstd::setstd::map

    intrusive_ptr<RCObject> my_bear = v[10];
    

    m["John"] = my_bear;
    

    期待どおりに動作します。

  8. ソースコードは、C++11のサポートが制限されたC++98コンパイラ(正確には、Visual Studio2008およびgcc4.6)を使用してコンパイルできる必要があります。

詳しくは

  • 私はBoostの初心者です Boostは非常に大きいので、それに慣れるのに少し時間が必要です。既存のすぐに使えるソリューションがあるかもしれませんが、そのようなソリューションに気付いていない可能性が高いです。 );
  • intrusive_ptrパフォーマンスを考慮して、の代わりに使用したいと思いshared_ptrますが、両方と他の提案を受け入れることができます。
  • make_shared()、、()が役立つかどうかはわかりません(ちなみに、allocate_shared()()Boostではあまり宣伝されていないようです-スマートポインタのメインページでも見つかりませんでした)。enable_shared_from_thisenable_shared_from_this
  • 「カスタムアロケータを書く」と聞いたことがありますが、複雑すぎるのではないかと思います。
  • 個人的RCObjectに継承すべきかどうか疑問に思います。boost::noncopyable
  • すべての要件を満たす既存の実装が見つかりませんでした。
  • フレームワークはゲームエンジンです。
  • 主なターゲットプラットフォームはAndroidとiOSです。
  • 引数依存のルックアップ(別名ケーニッヒルックアップ)を使用してそれらを実装する方法を知っintrusive_ptr_add_ref()ています。intrusive_ptr_release()
  • での使い方を知っていboost::atomic_size_t ますboost:intrusive_ptr

クラス定義

namespace zoo {
    class RCObject { ... };                  // Abstract
    class Animal : public RCObject { ... };  // Abstract
    class Bear : public Animal { ... };      // Concrete
    class Panda : public Bear { ... };       // Concrete
}

「非スマート」バージョン-createAnimal()[ファクトリメソッド]

zoo::Animal* createAnimal(bool isFacingExtinction, bool isBlackAndWhite) {
    // I wish I could call result->autorelease() at the end...
    zoo::Animal* result;

    if (isFacingExtinction) {
        if (isBlackAndWhite) {
            result = new Panda;
        } else {
            result = new Bear;
        }
    } else {
        result = 0;
    }

    return result;
}

「非スマート」バージョン-main()

int main() {
    // Part 1 - Construction
    zoo::RCObject* object1 = new zoo::Bear;
    zoo::RCObject* object2 = new zoo::Panda;
    zoo::Animal* animal1 = new zoo::Bear;
    zoo::Animal* animal2 = new zoo::Panda;
    zoo::Bear* bear1 = new zoo::Bear;
    zoo::Bear* bear2 = new zoo::Panda;
    //zoo::Panda* panda1 = new zoo::Bear;  // Should fail
    zoo::Panda* panda2 = new zoo::Panda;

    // Creating instances of RCObject on the stack should fail by following
    // the method described in the book MEC++1 Item 27.
    //
    //zoo::Bear b;                         // Should fail
    //zoo::Panda p;                        // Should fail

    // Part 2 - Object Assignment
    *object1 = *animal1;
    *object1 = *bear1;
    *object1 = *bear2;
    //*bear1 = *animal1;                   // Should fail

    // Part 3 - Cloning
    object1 = object2->clone();
    object1 = animal1->clone();
    object1 = animal2->clone();
    //bear1 = animal1->clone();            // Should fail

    return 0;
}

「スマート」バージョン[未完成!]

/* TODO: How to write the Factory Method? What should be returned? */

#include <boost/intrusive_ptr.hpp>

int main() {
    // Part 1 - Construction
    boost::intrusive_ptr<zoo::RCObject> object1(new zoo::Bear);
    boost::intrusive_ptr<zoo::RCObject> object2(new zoo::Panda);
    /* ... Skip (similar statements) ... */
    //boost::intrusive_ptr<zoo::Panda> panda1(new zoo::Bear); // Should fail
    boost::intrusive_ptr<zoo::Panda> panda2(new zoo::Panda);

    // Creating instances of RCObject on the stack should fail by following
    // the method described in the book MEC++1 Item 27. Unfortunately, there
    // doesn't exist a way to ban the user from declaring a raw pointer to
    // RCObject (and subclasses), all it relies is self discipline...
    //
    //zoo::Bear b;                         // Should fail
    //zoo::Panda p;                        // Should fail
    //zoo::Bear* pb;                       // No way to ban this
    //zoo::Panda* pp;                      // No way to ban this

    // Part 2 - Object Assignment
    /* ... Skip (exactly the same as "non-smart") ... */

    // Part 3 - Cloning
    /* TODO: How to write this? */

    return 0;
}

上記のコード(「スマートバージョン」)は、予想される使用パターンを示しています。この使用パターンがスマートポインターを使用するベストプラクティスに従っているかどうかはわかりません。そうでない場合は訂正してください。

同様の質問

参考文献

  • [ EC ++ 3 ]:効果的なC ++:プログラムとデザインを改善する55の特定の方法(第3版)Scott Meyers
    • 項目7:ポリモーフィック基本クラスで仮想デストラクタを宣言する

  • [ MEC ++ 1 ]:より効果的なC ++:プログラムとデザインを改善する35の新しい方法(第1版)Scott Meyers
    • 項目25:コンストラクターと非メンバー関数の仮想化
    • 項目27:ヒープベースのオブジェクトの要求または禁止。

記事

  • [ CP8394 ]:コードを強化するためのスマートポインター-CodeProject

  • [ DrDobbs ]:C++での侵入的に参照カウントされるオブジェクトの基本クラス-Dr.Dobb's
4

2 に答える 2

2

make_shared参照カウンターと同じ割り当てブロックにクラスのインスタンスを作成します。なぜintrusive_ptrパフォーマンスが向上すると思うのかわかりません。削除できない参照カウント機構がすでにある場合は素晴らしいのですが、ここではそうではありません。

クローンの場合、スマートポインターを受け取り、同じものを返す無料の関数として実装します。これはフレンドであり、ベース内のプライベート純粋仮想クローンメソッドを呼び出して、ベースへの共有ポインターを返し、派生への共有ポインターへの高速スマートポインターキャストを実行します。メソッドとしてcloneを使用する場合は、crtpを使用してこれを複製します(プライベートクローンにのような名前を付けsecret_cloneます)。これにより、オーバーヘッドがほとんどない共変のスマートポインター戻り型が得られます。

一連の基本クラスを持つCrtpでは、多くの場合、基本クラスと派生クラスの両方を渡す必要があります。crtpクラスは基本から派生し、通常self()は派生を返します。

ファクトリ関数はスマートポインタを返す必要があります。カスタム削除トリックを使用して、最後のクリーンアップのための破棄前のmethid呼び出しを取得できます。

完全にパラノイアの場合は、生のポインターまたはクラスへの参照を取得するほとんどの方法をブロックできます。スマートポインターのoperator*をブロックします。その場合、rawクラスへの唯一のルートは、への明示的な呼び出しoperator->です。

考慮すべき別のアプローチはunique_ptr、同じものへの参照です。共有所有権と生涯管理が必要ですか?それはいくつかの問題をより単純にします(共有所有権)。

弱いポインタがぶら下がっていると、メモリが共有されてリサイクルされるのを防ぐことに注意してください。

常にスマートポインタを使用することの重大な欠点は、スタックインスタンスまたはコンテナ内に直接インスタンスを配置できないことです。これらは両方とも、パフォーマンスを大幅に向上させる可能性があります。

于 2013-03-18T11:13:12.170 に答える
1
  1. スタック上にサブクラスのインスタンスを作成するRCObjectことは禁止されるべきです ([MEC++1][mec++1] 項目 27)。

あなたの根拠は何ですか?MEC++ は、「自殺できるオブジェクト」の例を示しています。これは、ゲーム フレームワークのコンテキストでは意味があるかもしれません。そうですか?

より単純な回避策を回避することを主張する場合は、十分にスマートなポインターで行うことができるはずです。

その場合は、スタック上にそのようなオブジェクトの配列を作成することも許可しないようにする必要があることに注意してくださいnew[]。これにより、単一のオブジェクトを削除することもできなくなります。おそらく、RCObjects をサブオブジェクト (他のクラスのメンバー) として使用することを禁止することもできます。これは、RCObject 値の使用を完全に禁止し、クライアント コードがスマート ポインターを介してのみそれらを処理できるようにすることを意味します。

  1. 生のポインターRCObject(およびサブクラス) を宣言/返すことは避ける必要があります (残念ながら、コンパイル エラーを発行してそれを強制する方法はないと思います)。

次に、「このオブジェクトに興味がありますが、それを維持するつもりはありません」と言う方法を持つための弱いポインタを持つことになります。

  1. ユーザーのサブクラス化は、サブクラスRCObjectの ["Factory Methods"][factory_method] を記述できる必要があります。戻り値が無視された (変数に割り当てられていない) 場合でも、メモリ リークは発生しないはずです。

このような関数は、参照カウントが 1 に等しい一時的なスマート ポインター オブジェクトを返します。この一時オブジェクトが別のオブジェクトの初期化に使用されない場合 (したがって、参照カウントがさらにインクリメントされる場合)、オブジェクトはクリーンアップされます。あなたは安全です。

  1. サブクラスのインスタンスは、またはコンテナー (または適切なもの)RCObjectに含めることができる必要があります。主に, and ;に似たものが必要です。std::boost::std::vectorstd::setstd::map

この種のことは (3) に同意しません。オブジェクトをヒープ上に個別に作成し、(値としてではなく) スマート ポインターを介して渡す必要があると主張する場合は、スマート ポインターのコンテナーも使用する必要があります。

  • intrusive_ptrパフォーマンスを考慮して、[ ][shared_ptr]の代わりに [ ][intrusive_ptr]を使用したいと思いshared_ptrますが、それらの両方や他の提案も受け入れます。

時期尚早に最適化していませんか?

また、侵入ポインターを使用すると、弱い参照を使用する可能性がなくなると思います。これは、前に述べたように、必要になる可能性が非常に高いです。

  • [ ][noncopyable]RCObjectから非公開に継承する必要があるかどうか疑問に思います。boost::noncopyable

値型の変数を許可せず、仮想クローンを提供する場合、パブリック コピー コンストラクターはおそらく必要ありません。プライベート コピー ctor を作成し、Clone を定義するときに使用できます。

于 2013-03-18T14:16:24.570 に答える