C++ 虚构造函数,没有 clone() [英] C++ Virtual Constructor, without clone()

查看:58
本文介绍了C++ 虚构造函数,没有 clone()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想对指向多态类的指针的 STL 容器执行深度复制".

我了解 Prototype 设计模式,它是通过 Virtual Ctor Idiom 实现的,如C++ FAQ Lite,第 20.8 项.
它简单明了:

struct ABC//抽象基类{虚拟 ~ABC() {}虚拟ABC *克隆()= 0;};结构 D1:公共 ABC{virtual D1 * clone() { return new D1( *this );}//协变返回类型};

一个深拷贝是:

for( i = 0; i 

clone());

缺点

正如 Andrei Alexandrescu 所说:

<块引用>

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

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

更好的设计?

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

<小时>

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

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

struct ImgOp{虚拟 ~ImgOp() {}布尔运行(图像和)= 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 v, Image &i ){for_each( v.begin(), v.end(),/* bind2nd( mem_fun( &ImgOp::run ), i ... ) 不记得语法 */);}

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

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

struct ImgOp{虚拟 ~ImgOp() {}布尔运行(图像和)= 0;虚拟 ImgOp * clone() = 0;//虚拟构造函数};struct CheckImageSize : public ImgOp {/* 没有克隆代码 */};struct CheckImageResolution : public ImgOp {/* 没有克隆代码 */};struct RotateImage : public ImgOp {/* 没有克隆代码 */};bool do_operations( vector v, Image &i ){//在另一个线程中向量v2;transform( v.begin(), v.end(),//复制指向-back_inserter( v2 ), mem_fun( &ImgOp::clone ) );//对象for_each( v.begin(), v.end(),/* bind2nd( mem_fun( &ImgOp::run ), i ... ) 不记得语法 */);}

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

困难的部分是避免新的 ImgOp 派生类的编写者编写任何与克隆相关的代码.(因为这是实现细节——这就是为什么我用 Curiously Recurring Pattern 驳回了 Paul 的回答.)

解决方案

仅供参考,这是我提出的设计.感谢 Paul 和 FredOverflow 的投入.(还有 Martin York 的评论.)

第 1 步,带有模板的编译时多态性

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

模板<类型名称 T >类 ImgOp{tm_t;//不是 ptr:当 ImgOp 被复制时,复制 ctor 和//赋值运算符执行对象的*真实*副本ImageOp ( const ImageOp &other ) : m_t( other .m_t ) {}ImageOp &运算符=( const ImageOp & );上市:ImageOp ( const T &p_t ) : m_t( p_t ) {}ImageOp T* clone() const { return new ImageOp( *this );}bool run( Image &i ) const { return m_t.run( i);}};//图像操作不需要从基类派生:它们必须提供//兼容的接口class CheckImageSize { bool run( Image &i ) const {...} };class CheckImageResolution { bool run( Image &i ) const {...} };class RotateImage { bool run( Image &i ) const {...} };

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

vector<图像操作>v;//编译错误,ImgOp 不是类型向量<图像运算<ImgOp1>>v;//只有一种操作:/

第 2 步,添加抽象级别

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

class AbstractImgOp{ImageOp T*克隆()const = 0;bool run(Image &i) const = 0;};模板<类型名称 T >ImgOp 类:公共 AbstractImgOp{//没有修改,特别是在 clone() 方法上,感谢//协变返回类型机制};

现在我们可以写:

vectorv;

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

AbstractImgOp *op1 = new AbstractImgOp;op1->w = ...;//编译错误,AbstractImgOp 没有op2->h = ...;//名为w"或h"的成员CheckImageSize *op1 = 新的 CheckImageSize;op1->w = ...;//美好的op1->h = ...;AbstractImgOp *op1Ptr = op1;//编译错误,CheckImageSize 不导出//来自 AbstractImgOp?令人困惑CheckImageSize op1;op1.w = ...;//美好的op1.h = ...;CheckImageResolution op2;//...v.push_back(new ImgOp(op1));//令人困惑!v.push_back(new ImgOp(op2));//啊

步骤#3,添加克隆指针"类

基于FredOverflow的解决方案,制作了一个克隆指针,使框架更易于使用.
然而,这个指针不需要被模板化,因为它被设计为只保存一种类型的 ptr——只有 ctor 需要被模板化:

class ImgOpCloner{AbstractImgOp *ptr;//Ptr 是实现多态行为所必需的ImgOpCloner &运算符=(const ImgOpCloner & );上市:模板<类型名称 T >ImgOpCloner(const T &t) : ptr(new ImgOp< T >( t ) ) {}ImgOpCloner( const AbstractImgOp &other ) : ptr( other.ptr->clone() ) {}~ImgOpCloner() { 删除 ptr;}AbstractImgOp * operator->() { return ptr;}抽象图像运维运算符*() { 返回 *ptr;}};

现在我们可以写:

CheckImageSize op1;op1.w = ...;//美好的op1.h = ...;CheckImageResolution op2;//...向量

I want to perform "deep copies" of an STL container of pointers to polymorphic classes.

I know about the Prototype design pattern, implemented by means of the Virtual Ctor Idiom, as explained in the C++ FAQ Lite, Item 20.8.
It is simple and straightforward:

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
};

A deep copy is then:

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

Drawbacks

As Andrei Alexandrescu states it:

The clone() implementation must follow the same pattern in all derived classes; in spite of its repetitive structure, there is no reasonable way to automate defining the clone() member function (beyond macros, that is).

Moreover, clients of ABC can possibly do something bad. (I mean, nothing prevents clients to do something bad, so, it will happen.)

Better design?

My question is: is there another way to make an abstract base class clonable without requiring derived classes to write clone-related code? (Helper class? Templates?)


Following is my context. Hopefully, it will help understanding my question.

I am designing a class hierarchy to perform operations on a class Image:

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

Image operations are user-defined: clients of the class hierarchy will implement their own classes derived from 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          { ... };
...

Multiple operations can be performed sequentially on an image:

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

If there are multiple images, the set can be split and shared over several threads. To ensure "thread-safety", each thread must have its own copy of all operation objects contained in v -- v becomes a prototype to be deep copied in each thread.

Edited: The thread-safe version uses the Prototype design pattern to enforce copy of pointed-to-objects -- not 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 */ );
}

This has sense when image operation classes are small: do not serialize accesses to unique instances of ImgOps, rather provide each thread with their own copies.

The hard part is to avoid writers of new ImgOp-derived classes to write any clone-related code. (Because this is implementation detail -- this is why I dismissed Paul's answers with the Curiously Recurring Pattern.)

解决方案

FYI, this is the design I came out with. Thank you Paul and FredOverflow for your inputs. (And Martin York for your comment.)

Step #1, Compile-time polymorphism with templates

Polymorphism is performed at compile-time using templates and implicit-interfaces:

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 {...} };

Now all the clone-related code lies within a unique class. However, it is now impossible to have a container of ImgOps templatized on different operations:

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

Step #2, Add a level of abstraction

Add a non-template base acting as an interface:

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
};

Now we can write:

vector< AbstractImgOp* > v;

But it becomes hard to manipulate image operation objects:

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

Step #3, Add a "cloning pointer" class

Based on the FredOverflow's solution, make a cloning pointer to make the framework simpler to use.
However, this pointer needs not to be templatized for it is designed to hold only one type of ptr -- only the ctor needs to be templatized:

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; }
};

Now we can write:

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

这篇关于C++ 虚构造函数,没有 clone()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆