1

この質問は、コピーとポインターのポリモーフィズムに関するものです。以下のコードを検討してください。通常のオブジェクトであるBaseDerivedの2 つのクラスがあります。次に、唯一のメンバーとしてBaseへのポインターを持つクラスFooを取得します。

Fooの典型的な使用法は、関数で説明されていmainます。への入力Foo::SetMemberXは、一時オブジェクトである場合とそうでない場合があります。

問題は、渡されたオブジェクトの適切なコピーを作成し、そのアドレスをtoFoo::SetMemberとして割り当てたいことです。Base*Foo::mMember

私は4つの可能な解決策を考え出すことができましたが、どれも私には非常にエレガントに見えません. 最初の 3 つは、以下のコードのFoo::SetMember1Foo::SetMember2、およびに示されていますFoo::SetMember3。4 番目のオプションは、メモリの割り当てをユーザーに任せることです (例: foo.SetMember(new Derived()))。これは、明らかなメモリの安全性の問題にはあまり望ましくありません。ユーザーではなく、 Fooがメモリ管理を担当する必要があります。

#include <iostream>

template <typename tBase, typename tPointer>
void ClonePointer(tBase*& destination, const tPointer* pointer)
{
    destination = static_cast<tBase*>(new tPointer(*pointer));
}

// Base can be a virtual class
class Base
{
public:

    virtual void Function()
    {
        std::cout << "Base::Function()" << std::endl;
    }

    virtual Base* Clone() const = 0;
};

class Derived : public Base
{
public:

    virtual void Function()
    {
        std::cout << "Derived::Function()" << std::endl;
    }

    virtual Base* Clone() const
    {
        return new Derived(*this);
    }
};

class Foo
{
public:

    Foo() : mMember(NULL) { }

    ~Foo()
    {
        if (mMember != NULL)
        {
            delete mMember;
            mMember = NULL;
        }
    }

    template <typename T>
    void SetMember1(const T& t)
    {
        if (mMember != NULL)
        {
            delete mMember;
            mMember = NULL;
        }

        ClonePointer(mMember, &t);
    }

    void SetMember2(const Base& b)
    {
        mMember = b.Clone();
    }

    template <typename T>
    void SetMember3(const T& t)
    {
        if (mMember != NULL)
        {
            delete mMember;
            mMember = NULL;
        }

        mMember = new T(t);
    }

    Base& GetMember()
    {
        return *mMember;
    }

private:

    Base* mMember;
};

int main(int argc, char** argv)
{
    {
        Foo f1;
        Foo f2;
        Foo f3;

        // The input may or may not be a tempoary/RValue reference

        f1.SetMember1(Derived());
        f2.SetMember2(Derived());
        f3.SetMember3(Derived());

        f1.GetMember().Function();
        f2.GetMember().Function();
        f3.GetMember().Function();
    }

    // Output:
    // Derived::Function();
    // Derived::Function();
    // Derived::Function();

    system("pause"); // for quick testing
}

最初の方法の問題 ( Foo::SetMember1) は、ランダムで自由なテンプレート関数とテンプレート アクセサがあることです (以下の 3 番目の方法の問題を参照してください)。

2 番目の方法 ( Foo::SetMember2) の問題は、すべての派生クラスが独自のClone関数を実装する必要があることです。Baseから派生する多くのクラスが存在するため、これはクラス ユーザーにとって定型コードになりすぎます。何らかの形でこれを自動化するか、実装されたテンプレート関数を使用して基本のCloneableクラスを (各Base派生クラスが明示的に呼び出す必要なしに) 作成できれば、これが理想的なソリューションになります。Clone

3 番目の方法 ( ) の問題は、FooFoo::SetMember3のテンプレート アクセサーが必要になることです。これは常に可能であるとは限りません。特に、テンプレート以外のクラスでは仮想テンプレート メソッドが許可されていないため ( Fooはテンプレート自体にすることはできません)、必要な機能である可能性があります。

私の質問は次のとおりです。

  1. これらは私が持っている唯一のオプションですか?

  2. 私が見逃しているこの問題に対するより優れた、よりエレガントな解決策はありますか?

  3. 基本のCloneableクラスを作成し、そこからBaseを派生させて、クローンを自動的に作成する方法はありますDerivedType::Clone()か?

4

2 に答える 2

3

Clonableこれは、任意の深さに継承できる多かれ少なかれ堅牢なクラスです。CRTP と Alecsandrescu スタイルのインターリーブ継承パターンを使用します。

#include <iostream>

// set up a little named template parameters rig
template <class X> struct Parent{};
template <class X> struct Self{};
template<class A, class B> struct ParentChild;

// can use ...< Parent<X>, Self<Y> >...
template<class A, class B> struct ParentChild< Parent<A>, Self<B> >
{
    typedef A parent_type;
    typedef B child_type;
};

// or ...< Self<Y>, Parent<X> >
template<class A, class B> struct ParentChild< Self<B>, Parent<A> >
{
    typedef A parent_type;
    typedef B child_type;
};

// nothing, really
struct Nada
{
    // except the virtual dtor! Everything clonable will inherit from here.
    virtual ~Nada() {}
};

// The Clonable template. Accepts two parameters: 
// the child class (as in CRTP), and the parent class (one to inherit from)
// In any order.
template <class A, class B = Parent<Nada> > class Clonable : 
    public ParentChild<A,B>::parent_type
{
  public:
    // a nice name to refer to in the child class, instead of Clonable<A,B>
    typedef Clonable Parent;

    // this is our child class
    typedef typename ParentChild<A,B>::child_type child_type;

    // This is the clone() function returning the cloned object
    // Non-virtual, because the compiler has trouble with covariant return
    // type here. We have to implemens something similar, by having non-virtual
    // that returns the covariant type calling virtual that returns the 
    // base type, and some cast.
    child_type* clone()
    {
        return static_cast<child_type*>(private_clone());
    }

    // forward some constructor, C++11 style
    template<typename... Args> Clonable(Args&&... args): 
        ParentChild<A,B>::parent_type(args...) {}

  private:

    // this is the main virtual clone function
    // allocates the new child_type object and copies itself
    // with the copy constructor
    virtual Nada* private_clone() 
    {
        // we *know* we're the child_type object
        child_type* me = static_cast<child_type*>(this);
        return new child_type(*me);
    };
};

// Test drive and usage example

class Foo : public Clonable < Self<Foo> >
{
  public: 
    Foo (int) { std::cout << "Foo::Foo(int)\n"; }
    Foo (double, char) { std::cout << "Foo::Foo(double, char)\n"; }
    Foo (const Foo&) { std::cout << "Foo::Foo(Foo&)\n"; }
};

class Bar : public Clonable < Self<Bar>, Parent<Foo> >
{
  public:
    // cannot say Bar (int i) : Foo(i), unfortunately, because Foo is not
    // our immediate parent
    // have to use the Parent alias 
    Bar (int i) : Parent(i) 
        { std::cout << "Bar::Bar(int)\n"; }
    Bar (double a, char b) : Parent(a, b) 
        { std::cout << "Bar::Bar(double, char)\n"; }
    Bar (const Bar& b) : Parent(b) 
        { std::cout << "Bar::Bar(Bar&)\n"; }

    ~Bar() { std::cout << "Bar::~Bar()\n"; }
};

int main ()
{
    Foo* foo1 = new Bar (123);
    Foo* foo2 = foo1->clone(); // this is really a Bar
    delete foo1;
    delete foo2;
}
于 2012-07-25T20:49:06.650 に答える
2

2 番目の方法では、CRTPを使用でき、派生クラスごとに clone メソッドを記述する必要はありません。

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

template <typename Derived>
struct CloneableBase : public Base {
  virtual Base *clone() const {
    return new Derived(static_cast<Derived const&>(*this));
  }
};

struct Derived: CloneableBase<Derived> {};
于 2012-07-25T19:24:53.130 に答える