25

Scott Myers が書いたように、C++ の型システムの緩和を利用して clone() を宣言し、宣言されている実際の型へのポインターを返すことができます。

class Base
{
    virtual Base* clone() const = 0;
};

class Derived : public Base
{
    virtual Derived* clone() const
};

コンパイラは、clone() がオブジェクトの型へのポインターを返すことを検出し、Derived がそれをオーバーライドして、派生へのポインターを返すことを許可します。

次のように、所有権のセマンティクスの転送を意味するスマート ポインターを clone() が返すようにすることが望ましいでしょう。

class Base
{
   virtual std::auto_ptr<Base> clone() const = 0;
};

class Derived : public Base
{
    virtual std::auto_ptr<Derived> clone() const;
};

残念ながら、規則の緩和はテンプレート化されたスマート ポインターには適用されず、コンパイラーはオーバーライドを許可しません。

したがって、次の 2 つのオプションが残っているようです。

  1. clone() に「ダム」ポインターを返させ、クライアントがそれを破棄する責任があることを文書化します。
  2. clone() がスマート ベース ポインターを返すようにし、必要に応じてクライアントが dynamic_cast を使用して派生ポインターに保存するようにします。

これらのアプローチのいずれかが優先されますか? または、所有権のセマンティクスの譲渡を食べて、強力な型の安全性も確保する方法はありますか?

4

8 に答える 8

29

Public non-virtual / Private virtual パターンを使用します。

class Base {
    public:
    std::auto_ptr<Base> clone () { return doClone(); }
    private:
    virtual Base* doClone() { return new (*this); }
};
class Derived : public Base {
    public:
    std::auto_ptr<Derived> clone () { return doClone(); }
    private:
    virtual Derived* doClone() { return new (*this); }
};
于 2008-11-04T08:48:31.420 に答える
19

構文はあまり良くありませんが、上記のコードにこれを追加すると、すべての問題が解決されませんか?

template <typename T>
std::auto_ptr<T> clone(T const* t)
{
    return t->clone();
}
于 2008-11-03T21:39:06.153 に答える
7

この場合、関数のセマンティクスは非常に明確であるため、混乱する余地はほとんどないと思います。したがって、共変バージョン (実際の型へのダム ポインターを返すバージョン) を簡単な良心で使用できると思います。呼び出し元は、プロパティが転送された新しいオブジェクトを取得していることを認識します。

于 2008-11-03T21:28:33.103 に答える
5

ユースケースによって異なります。clone動的型がわかっている派生オブジェクトを呼び出す必要があると思われる場合(動的型を知らなくcloneてもコピーできるようにすることが重要であることを思い出してください)、おそらくダム ポインターを返し、それをスマート オブジェクトにロードする必要があります。呼び出しコードのポインター。そうでない場合は、smart_ptr を返すだけでよいので、すべてのオーバーライドで自由に返すことができます。

于 2008-11-03T21:02:57.867 に答える
2

Tr1::shared_ptr<>生のポインターのようにキャストできます。

shared_ptr<Base>clone() がポインターを返すようにすることは、かなりクリーンなソリューションだと思います。コンパイル時にクローン オブジェクトの種類を判別できない場合shared_ptr<Derived>は、tr1::static_pointer_cast<Derived>またはを使用してポインターをキャストできます。tr1::dynamic_pointer_cast<Derived>

オブジェクトの種類が予測可能であることを確認するには、次のような shared_ptr のポリモーフィック キャストを使用できます。

template <typename R, typename T>
inline std::tr1::shared_ptr<R> polymorphic_pointer_downcast(T &p)
{
    assert( std::tr1::dynamic_pointer_cast<R>(p) );
    return std::tr1::static_pointer_cast<R>(p);
}

アサートによって追加されたオーバーヘッドは、リリース バージョンでは破棄されます。

于 2008-11-03T22:41:42.907 に答える
1

これがorboost::intrusive_ptrの代わりに使用する理由の 1 つです。生のポインターには参照カウントが含まれており、このような状況ではよりシームレスに使用できます。shared_ptrauto/unique_ptr

于 2008-11-03T21:41:22.890 に答える
1

C++ 14 のMSalter の回答を更新する:

#include <memory>

class Base
{
public:
    std::unique_ptr<Base> clone() const
    {
        return do_clone();
    }
private:
    virtual std::unique_ptr<Base> do_clone() const
    {
        return std::make_unique<Base>(*this);
    }
};

class Derived : public Base
{
private:
    virtual std::unique_ptr<Base> do_clone() const override
    {
        return std::make_unique<Derived>(*this);
    }
}
于 2016-08-01T22:53:30.757 に答える
0

基本型のスマート ポインター ラッパーを返す virtual clone() と、正しい型のスマート ポインターを返す非仮想 clone2() の 2 つのメソッドを使用できます。

clone2 は明らかにクローンの観点から実装され、キャストをカプセル化します。

そうすれば、コンパイル時に知っている最も派生したスマート ポインターを取得できます。これは、全体として最も派生した型ではないかもしれませんが、コンパイラが利用できるすべての情報を使用します。

もう 1 つのオプションは、期待する型を受け入れる clone のテンプレート バージョンを作成することですが、呼び出し元の負担が大きくなります。

于 2008-11-03T21:22:52.613 に答える