8

Boostライブラリのドキュメントから私はこれを読みました:

概念的には、スマートポインターは、ポイントされたオブジェクトを所有していると見なされるため、オブジェクトが不要になったときにオブジェクトを削除する責任があります。

非常に単純な問題があります。コピー可能で割り当て可能なクラスのポインター属性にRAIIを使用したいのです。

コピーと割り当ての操作は深くする必要があります。すべてのオブジェクトには、実際のデータの独自のコピーが必要です。また、属性に対してRTTIを使用できる必要があります(属性のタイプは実行時に決定される場合もあります)。

コピー可能なスマートポインターの実装を検索する必要がありますか(データが小さいため、コピーオンライトポインターは必要ありません)、またはこの回答に示すように、コピー操作をオブジェクトのコピーコンストラクターに委任しますか?

コピー可能で割り当て可能なクラスの単純なRAIIには、どのスマートポインターを選択しますか?(クラスコピーコンストラクターと代入演算子にコピー/代入演算を委任したunique_ptrが適切な選択になると思いますが、よくわかりません)

これは、生のポインタを使用した問題の擬似コードです。これは単なる問題の説明であり、実行中のC++コードではありません。

// Operation interface
class ModelOperation
{
    public: 
        virtual void operate = (); 
};

// Implementation of an operation called Special 
class SpecialModelOperation
:
    public ModelOperation
{
    private:
        // Private attributes are present here in a real implementation. 

    public: 

        // Implement operation
        void operate () {}; 
};

// All operations conform to ModelOperation interface
// These are possible operation names: 
// class MoreSpecialOperation; 
// class DifferentOperation; 

// Concrete model with different operations
class MyModel 
{
    private: 
        ModelOperation* firstOperation_; 
        ModelOperation* secondOperation_;  

    public:

        MyModel()
            : 
                firstOperation_(0), 
                secondOperation_(0)
        {
            // Forgetting about run-time type definition from input files here.
            firstOperation_  = new MoreSpecialOperation(); 
            secondOperation_ = new DifferentOperation(); 
        }

        void operate()
        {
            firstOperation_->operate(); 
            secondOperation_->operate();
        }

        ~MyModel() 
        {
            delete firstOperation_; 
            firstOperation_ = 0; 

            delete secondOperation_; 
            secondOperation_ = 0; 
        }
};

int main()
{

    MyModel modelOne; 

    // Some internal scope
    {
        // I want modelTwo to have its own set of copied, not referenced 
        // operations, and at the same time I need RAII to for the operations, 
        // deleting them automatically as soon as it goes out of scope. 
        // This saves me from writing destructors for different concrete models.  
        MyModel modelTwo (modelOne); 
    }


    return 0;
}
4

5 に答える 5

6

タイプに関するいくつかの要件を受け入れる場合、これは、すべてのタイプに仮想クローン関数を実装する必要なしに実行できます。特定の要件は、タイプにアクセス可能なコピーコンストラクターがあることです。これは、偶発的なスライスの可能性があるため、望ましくないと見なされるものもあります。ただし、フレンドリングを適切に使用することで、その欠点を軽減できる場合があります。

そのようなことが許容できる場合は、コピー機能を提供するインターフェースの下で派生型を消去することでこれを回避できます。

template <typename Base>
struct clonable {
    // virtual copy
    // this clone function will be generated via templates
    // no boilerplate is involved
    virtual std::unique_ptr<clonable<Base>> clone() const = 0;

    // expose the actual data
    virtual Base* get() = 0;
    virtual Base const* get() const = 0;

    // virtual destructor
    // note that this also obviates the need for a virtual destructor on Base
    // I would probably still make it virtual, though, just in case
    virtual ~clonable() = default;
};

このインターフェースは、最も派生した型でテンプレート化されたクラスによって実装されるため、コピーコンストラクターを介して通常のコピーを作成する方法を知っています。

template <typename Base, typename Derived>
struct clonable_holder : clonable<Base> {
    // I suppose other constructors could be provided
    // like a forwarding one for emplacing, but I am going for minimal here
    clonable_holder(Derived value)
    : storage(std::move(value)) {}

    // here we know the most derived type, so we can use the copy constructor
    // without risk of slicing
    std::unique_ptr<clonable<Base>> clone() const override {
        return std::unique_ptr<clonable<Base>>(new clonable_holder(storage));
    }

    Base* get() override { return &storage; }
    Base const* get() const override { return &storage; }

private:
    Derived storage;
};

これにより、追加の定型文なしで仮想コピー機能が生成されます。これで、この上にスマートポインターのようなクラスを構築できます(ポインターセマンティクスを提供しないため、スマートポインターではなく、代わりに値セマンティクスを提供します)。

template <typename Base>
struct polymorphic_value {
    // this constructor captures the most derived type and erases it
    // this is a point where slicing may still occur
    // so making it explicit may be desirable
    // we could force constructions through a forwarding factory class for extra safety
    template <typename Derived>
    polymorphic_value(Derived value)
    : handle(new clonable_holder<Base, Derived>(std::move(value))) {
        static_assert(std::is_base_of<Base, Derived>::value,
            "value must be derived from Base");
    }

    // moving is free thanks to unique_ptr
    polymorphic_value(polymorphic_value&&) = default;
    polymorphic_value& operator=(polymorphic_value&&) = default;

    // copying uses our virtual interface
    polymorphic_value(polymorphic_value const& that)
    : handle(that.handle->clone()) {}
    polymorphic_value& operator=(polymorphic_value const& that) {
        handle = that.handle->clone();
        return *this;
    }

    // other useful constructors involve upcasting and so on

    // and then useful stuff for actually using the value
    Base* operator->() { return handle.get(); }
    Base const* operator->() const { return handle.get(); }
    // ...

private:
    std::unique_ptr<clonable<Base>> handle;
};

これは最小限のインターフェースですが、ここから簡単に具体化して、より多くの使用シナリオをカバーすることができます。

于 2012-12-17T12:12:02.800 に答える
5

少し遅れていますが、将来の視聴者のために:ヘッダーのみのライブラリAuroraとそのSmartPtrチュートリアルには、すぐに使用できる実装があります。Auroraを使用すると、スマートポインタを介してディープコピーを実装するのは簡単です。次のコードは、コピー可能なすべてのタイプで機能しますT

aurora::CopiedPtr<T> first(new T);
aurora::CopiedPtr<T> second = first; // deep copy

これにより、クラスにポインターメンバーがある場合、Big Three/Fiveを実装する必要がなくなることがよくあります。

于 2016-01-10T13:56:09.007 に答える
1

すぐに使える実現について聞いたことがありませんが、自分で簡単に行うことができます。

まず最初に、保存されたオブジェクトのコピーを返す、仮想クローンメソッドを持つテンプレートラッパークラスを作成する必要があります。そして、コピー可能なそのクラスのポリモフィックホルダーを書きます

チェックされた削除を忘れないでください http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Checked_delete

于 2012-12-17T10:48:26.097 に答える
1

別のスマートポインタオブジェクトが作成されるたびに、オブジェクトの新しいコピーを作成するスマートポインタを作成できる必要があるようです。(そのコピーが「深い」かどうかは、オブジェクトのコンストラクター次第だと思います。格納しているオブジェクトは、私たちが知っている限り、所有権の深いレベルを持っている可能性があるため、「深い」は次の意味に依存します。私たちの目的の主な目的は、既存のオブジェクトへのポインターを取り出すのではなく、別のオブジェクトからの参照を使用してスマートポインターを構築するときに、別個のオブジェクトを作成するものが必要なことです。)

質問を正しく理解した場合は、仮想クローンメソッドが必要になります。派生クラスのコンストラクターを正しく呼び出す方法は他にありません。

struct Clonable {
  virtual ~Clonable() {}
  virtual Clonable* clone() = 0;
};
struct AutoPtrClonable {
  AutoPtrClonable(Clonable* cl=0) : obj(cl) { }
  AutoPtrClonable(const AutoPtrClonable& apc) : obj(apc.obj->clone()) { }
  ~AutoPtrClonable() { delete obj; }
  // operator->, operator*, etc
  Clonable* obj;
};

サンプルコードを使用するには、テンプレートなどにします。

于 2012-12-17T10:50:22.747 に答える
0

あなたには2つの解決策があります(実際にはもっとたくさんありますが、これらは私にとって最も理にかなっています:)):

まず、を使用できますstd::unique_ptr。これは、ポインタごとに1つのインスタンスを持つ必要があるため、優れたソリューションです。(std::shared_ptr代わりに使用することもできますが、コードを明示的に追加しない場合、shared_ptrのコピーと割り当ては「共有」されます-特に避けたいもの)。

を使用する場合std::unique_ptr、コピーコンストラクターと代入演算子は明示的にディープコピーする必要があります(cloneポインターのインターフェイスの仮想メソッド、またはunique_ptrコンストラクターの呼び出しで新しい演算子のいずれかを使用)。

第二に、あなたはあなた自身を転がすことができます。複雑なことは何もありません。小さな(10〜20行程度)ユーティリティクラスについて話しています。

個人的には、このスマートポインタクラスを1つの場所で使用する必要がある場合は、std::unique_ptrを使用します。それ以外の場合(複数のポインター、同じ動作)、私は自分自身をロールバックするので、(DRYの原則を維持するために)多くのインスタンスでディープコピーを繰り返す必要はありません。

于 2012-12-17T12:01:14.523 に答える