7

我想对指向多态类的指针的 STL 容器执行“深拷贝” 。

我知道原型设计模式,它是通过Virtual Ctor Idiom实现的,如C++ FAQ Lite, Item 20.8中所述。
它简单明了:

struct ABC // Abstract Base Class
{
    virtual ~ABC() {}
    virtual ABC * clone() = 0;
};
struct D1 : public ABC
{
    virtual D1 * clone() { return new D1( *this ); } // Covariant Return Type
};

那么深拷贝是:

for( i = 0; i < oldVector.size(); ++i )
    newVector.push_back( oldVector[i]->clone() );

缺点

正如 Andrei Alexandrescu所说

clone()实现必须在所有派生类中遵循相同的模式;尽管其结构重复,但没有合理的方法来自动定义clone()成员函数(即除了宏之外)。

此外,客户ABC可能会做坏事。(我的意思是,没有什么能阻止客户做坏事,所以它发生。)

更好的设计?

我的问题是:是否有另一种方法可以使抽象基类可克隆,而无需派生类编写与克隆相关的代码?(助手类?模板?)


以下是我的背景。希望这将有助于理解我的问题。

我正在设计一个类层次结构来对一个类执行操作Image

struct ImgOp
{
    virtual ~ImgOp() {}
    bool run( Image & ) = 0;
};

图像操作是用户定义的:类层次结构的客户端将实现他们自己的类派生自ImgOp

struct CheckImageSize : public ImgOp
{
    std::size_t w, h;
    bool run( Image &i ) { return w==i.width() && h==i.height(); }
};
struct CheckImageResolution { ... };
struct RotateImage          { ... };
...

可以对图像按顺序执行多个操作:

bool do_operations( vector< ImgOp* > v, Image &i )
{
    for_each( v.begin(), v.end(),
        /* bind2nd( mem_fun( &ImgOp::run ), i ... ) don't remember syntax */ );
}

如果有多个图像,则可以将集合拆分并在多个线程上共享。为确保“线程安全”,每个线程必须拥有自己的所有操作对象的副本v--v成为要在每个线程中深度复制的原型。

编辑:线程安全版本使用原型设计模式来强制复制指向对象——而不是 ptrs:

struct ImgOp
{
    virtual ~ImgOp() {}
    bool run( Image & ) = 0;
    virtual ImgOp * clone() = 0; // virtual ctor
};

struct CheckImageSize : public ImgOp       { /* no clone code */ };
struct CheckImageResolution : public ImgOp { /* no clone code */ };
struct RotateImage : public ImgOp          { /* no clone code */ };

bool do_operations( vector< ImgOp* > v, Image &i )
{
    // In another thread
    vector< ImgOp* > v2;
    transform( v.begin(), v.end(),                       // Copy pointed-to-
        back_inserter( v2 ), mem_fun( &ImgOp::clone ) ); // objects
    for_each( v.begin(), v.end(),
        /* bind2nd( mem_fun( &ImgOp::run ), i ... ) don't remember syntax */ );
}

当图像操作类很小时,这很有意义:不要序列化对ImgOps 的唯一实例的访问,而是为每个线程提供自己的副本。

困难的部分是避免新ImgOp派生类的编写者编写任何与克隆相关的代码。(因为这是实现细节——这就是为什么我用奇怪的重复模式驳回了保罗的答案。)

4

3 回答 3

7

您可以使用奇怪的递归模式,但它可能会降低您的代码的可读性。您仍然需要复制构造函数。它的工作原理如下。

struct ABC // Abstract Base Class
{
    virtual ~ABC() {}
    virtual ABC * clone() const = 0;
};



template <class TCopyableClass>
struct ClonableABC : public ABC
{
    virtual ABC* clone() const {
       return new TCopyableClass( *(TCopyableClass*)this );
    } 
};


struct SomeABCImpl : public ClonableABC<SomeABCImpl>
{};
于 2010-05-04T14:17:49.537 回答
2

然后是深拷贝:[for循环]

您让客户端显式克隆向量。我不确定这是否能回答您的问题,但我建议使用智能指针向量,以便克隆自动发生。

std::vector<cloning_pointer<Base> > vec;
vec.push_back(cloning_pointer<Base>(new Derived()));

// objects are automatically cloned:
std::vector<cloning_pointer<Base> > vec2 = vec;

当然,您不希望在调整矢量或其他东西的大小时发生这些隐式副本,因此您需要能够区分副本和移动。这是我的 C++0x 玩具实现cloning_pointer,您可能需要根据自己的需要进行调整。

#include <algorithm>

template<class T>
class cloning_pointer
{
    T* p;

public:

    explicit cloning_pointer(T* p)
    {
        this->p = p;
    }

    ~cloning_pointer()
    {
        delete p;
    }

    cloning_pointer(const cloning_pointer& that)
    {
        p = that->clone();
    }

    cloning_pointer(cloning_pointer&& that)
    {
        p = that.p;
        that.p = 0;
    }

    cloning_pointer& operator=(const cloning_pointer& that)
    {
        T* q = that->clone();
        delete p;
        p = q;
        return *this;
    }

    cloning_pointer& operator=(cloning_pointer&& that)
    {
        std::swap(p, that.p);
        return *this;
    }

    T* operator->() const
    {
        return p;
    }

    T& operator*() const
    {
        return *p;
    }
};

Julien:&&不是“ref of ref”,它是一个右值引用,只绑定到可修改的右值。有关右值引用及其工作原理的概述,请参阅此出色的(但遗憾的是有些过时)教程视频。

于 2010-05-04T16:25:52.570 回答
2

仅供参考,这是我出来的设计。感谢 Paul 和 FredOverflow 的投入。(马丁·约克(Martin York)请您发表评论。)

步骤 #1,使用模板的编译时多态性

多态性在编译时使用模板和隐式接口执行:

template< typename T >
class ImgOp
{
    T m_t; // Not a ptr: when ImgOp is copied, copy ctor and
           // assignement operator perform a *real* copy of object
    ImageOp ( const ImageOp &other ) : m_t( other .m_t ) {}
    ImageOp & operator=( const ImageOp & );
public:
    ImageOp ( const T &p_t ) : m_t( p_t ) {}
    ImageOp<T> * clone() const { return new ImageOp<T>( *this ); }
    bool run( Image &i ) const { return m_t.run( i); }
};

// Image operations need not to derive from a base class: they must provide
// a compatible interface
class CheckImageSize       { bool run( Image &i ) const {...} };
class CheckImageResolution { bool run( Image &i ) const {...} };
class RotateImage          { bool run( Image &i ) const {...} };

现在所有与克隆相关的代码都位于一个唯一的类中。但是,现在不可能ImgOp在不同的操作上模板化 s 的容器:

vector< ImgOp > v;           // Compile error, ImgOp is not a type
vector< ImgOp< ImgOp1 > > v; // Only one type of operation :/

步骤 #2,添加抽象级别

添加一个非模板库作为接口:

class AbstractImgOp
{
    ImageOp<T> * clone() const = 0;
    bool run( Image &i ) const = 0;
};

template< typename T >
class ImgOp : public AbstractImgOp
{
    // No modification, especially on the clone() method thanks to
    // the Covariant Return Type mechanism
};

现在我们可以写:

vector< AbstractImgOp* > v;

但是操作图像操作对象变得困难:

AbstractImgOp *op1 = new AbstractImgOp;
    op1->w = ...; // Compile error, AbstractImgOp does not have
    op2->h = ...; // member named 'w' or 'h'

CheckImageSize *op1 = new CheckImageSize;
    op1->w = ...; // Fine
    op1->h = ...;
AbstractImgOp *op1Ptr = op1; // Compile error, CheckImageSize does not derive
                             // from AbstractImgOp? Confusing

CheckImageSize op1;
    op1.w = ...; // Fine
    op1.h = ...;
CheckImageResolution op2;
    // ...
v.push_back( new ImgOp< CheckImageSize >( op1 ) );       // Confusing!
v.push_back( new ImgOp< CheckImageResolution >( op2 ) ); // Argh

步骤#3,添加一个“克隆指针”类

基于 FredOverflow 的方案,做一个克隆指针,让框架更简单易用。
但是,这个指针不需要模板化,因为它被设计为只保存一种类型的 ptr——只有 ctor 需要模板化:

class ImgOpCloner
{
    AbstractImgOp *ptr; // Ptr is mandatory to achieve polymorphic behavior
    ImgOpCloner & operator=( const ImgOpCloner & );
public:
    template< typename T >
    ImgOpCloner( const T &t ) : ptr( new ImgOp< T >( t ) ) {}
    ImgOpCloner( const AbstractImgOp &other ) : ptr( other.ptr->clone() ) {}
    ~ImgOpCloner() { delete ptr; }
    AbstractImgOp * operator->() { return ptr; }
    AbstractImgOp & operator*() { return *ptr; }
};

现在我们可以写:

CheckImageSize op1;
    op1.w = ...; // Fine
    op1.h = ...;
CheckImageResolution op2;
    // ...
vector< ImgOpCloner > v;
v.push_back( ImgOpCloner( op1 ) ); // This looks like a smart-ptr, this is not
v.push_back( ImgOpCloner( op2 ) ); // confusing anymore -- and intent is clear
于 2010-05-10T08:51:05.923 回答