C++虚拟构造函数,没有clone()

Jul*_*n-L 8 c++ prototype clone virtual-copy

我想执行指向多态类指针的STL容器的"深层副本" .

我知道Prototype设计模式,通过Virtual Ctor Idiom实现,如C++ FAQ Lite,第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
};
Run Code Online (Sandbox Code Playgroud)

然后是深层复制:

for( i = 0; i < oldVector.size(); ++i )
    newVector.push_back( oldVector[i]->clone() );
Run Code Online (Sandbox Code Playgroud)

缺点

正如Andrei Alexandrescu 所述:

clone()实施必须遵循所有派生类相同的模式; 尽管它具有重复的结构,但没有合理的方法来自动定义clone()成员函数(超出宏,即).

而且,客户ABC可能做坏事.(我的意思是,没有什么能阻止客户做坏事,所以,它发生.)

更好的设计?

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


以下是我的背景.希望它有助于理解我的问题.

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

struct ImgOp
{
    virtual ~ImgOp() {}
    bool run( Image & ) = 0;
};
Run Code Online (Sandbox Code Playgroud)

图像操作是用户定义的:类层次结构的客户端将实现自己的类派生自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          { ... };
...
Run Code Online (Sandbox Code Playgroud)

可以在图像上顺序执行多个操作:

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

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

编辑:线程安全版本使用Prototype设计模式来强制执行指向对象的副本- 而不是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 */ );
}
Run Code Online (Sandbox Code Playgroud)

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

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

ful*_*ton 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>
{};
Run Code Online (Sandbox Code Playgroud)

  • +1好方法.正如Tyler在评论中提到的那样,你不再有一个协变的返回类型.我从来不明白为什么那么重要.我称之为克隆方法的地方,无论如何我都不知道派生类型. (2认同)

Jul*_*n-L 2

仅供参考,这是我提出的设计。感谢 Paul 和 FredOverflow 的投入。(还有马丁·约克的评论。)

步骤#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 {...} };
Run Code Online (Sandbox Code Playgroud)

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

vector< ImgOp > v;           // Compile error, ImgOp is not a type
vector< ImgOp< ImgOp1 > > v; // Only one type of operation :/
Run Code Online (Sandbox Code Playgroud)

步骤#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
};
Run Code Online (Sandbox Code Playgroud)

现在我们可以写:

vector< AbstractImgOp* > v;
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

步骤#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; }
};
Run Code Online (Sandbox Code Playgroud)

现在我们可以写:

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
Run Code Online (Sandbox Code Playgroud)