The clone pattern

 The clone pattern

  Jan 27, 2010 at 11:31pm
jsmith (5804)
In order to copy an object, you have to know at compile time the
object's type, because the type is the "name" of the copy constructor:

1
2
3
4
void copy_me( const std::string& s ) {     
    std::string  s_copy( s );
    std::string* p_s_copy = new std::string( s );
}


I know at compile time that s has type "std::string" because it says
so in the parameter list. But what if type of s was a base class?

1
2
3
4
5
6
class Base {};
class Derived : public Base {};

void copy_me( const Base& b ) {
    Base b_copy( b );   // ????
}


That doesn't quite work, because I can call copy_me() with a derived
class instance, in which case the function would want to instantiate
a Derived object, not a Base object. But at compile time there is
simply no way for me to know that. Indeed, I could even call copy_me()
with Base instances in one place, Derived in another, and something
else (derived from Base or Derived) in a third.

How can this problem be solved?

The clone pattern was implemented for exactly this reason. The
clone pattern looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Depending upon your needs, you might not require a base class
// clonable concept. It would only be needed if you need to store
// clonable objects polymorphically.
struct clonable {
    virtual ~clonable() {}
    virtual clonable* clone() const = 0;
};

class Base : public clonable {
  public:
     virtual Base* clone() const
        { return new Base( *this ); }
};

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


And now, copy_me looks like:

1
2
3
4
void copy_me( const Base& b ) {
    Base* clone = b.clone();
    // delete clone; 
};


And I've successfully invoked Base's copy constructor if the
"real type" of b is Base, and Derived's copy constructor if the
"real type" of b is Derived.

It is worth mentioning here that this technique exploits the fact that
the compiler does not consider the return type of the function when
determining whether or not a derived class virtual method has overridden
a base class one with the same name.
  Feb 28, 2010 at 3:21am
hamsterman (4538)
In order to copy an object, you have to know at compile time the
object's type

I think you can solve this problem in another way. If you know object's type at runtime you can copy it. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Base{
    virtual int Type() const{
        return 0;
    }
};
struct Derived: Base{
    virtual int Type() const{
        return 1;
    }
};

void copy_me(const Base& b){
    Base* object = 0;
    switch(b.Type()){
        case 0://Base
            object = new Base(b);
            break;
        case 1://Derived
            object = new Derived((Derived&)b);
            break;
    }



c++ 中虚拟克隆(virtual clone)

又看了一遍more effective c++, 注意到以前没怎么记住的virtual clone技术。

复制代码
class  Base {
public :
    
virtual  Base *  clone();
};

class  Impl1 {
public :
    
virtual  Impl1 *  clone(){ return   new  Impl1( * this );}
};

class  Impl2 {
public :
    
virtual  Impl2 *  clone(){ return   new  Impl2( * this; );}
};
复制代码

初一看没什么特别的。clone只是调用了拷贝构造函数。确实,在没有类层次结构的情况下,一个这样的clone()并不会比直接写拷贝构造有用。

奇特的是, 这里3个clone的返回类型是不一样的,但是编译器依然认为后两者是对基类clone()的实现!事实上不只是指针,返回引用也可以达到同样的效果。(题外话,虽然这里clone的语义显然只能返回指针,没人会返回一个局部对象的引用)。

 

 正是由于这种特性,使得以下实现成为可能:

 

复制代码
class  Container {
public :
    Container(
const  Container &  c) {
        
for  (List < Base *> ::const_iterator it  =  c.items.begin(); it  !=  c.items.end();  ++ it) {
            items.push_back(
* it -> clone());
    }
private :
    List
< Base *>  items;
};
复制代码

 这样,每次调用的都是动态类型的clone函数,尽管他们返回的是不同类型,依然具备多态特性。(再扯一句题外话,这里容器里保存指针是很危险的,显然通过拷贝构造的Container对象必须在析构函数里负责delete他们。但是,倘若该Container有其他方式,比如某个push函数,保存指针,那么, container是否有权在析构是删除这些指针就是个问题了。 所以,当容器中不得不使用指针时,还是是最好用shared_ptr或auto_ptr)

 

 想起昨天在项目代码中也看到了一个clone()的实现。一看竟然没用到虚拟克隆。于是又这样的代码:

 

复制代码
Address::ptr
Address::create(
const  sockaddr  * name, socklen_t nameLen)
{
    MORDOR_ASSERT(name);
    Address::ptr result;
    
switch  (name -> sa_family) {
        
case  AF_INET:
            result.reset(
new  IPv4Address());
            MORDOR_ASSERT(nameLen 
<=  result -> nameLen());
            memcpy(result
-> name(), name, nameLen);
            
break ;
        
case  AF_INET6:
            result.reset(
new  IPv6Address());
            MORDOR_ASSERT(nameLen 
<=  result -> nameLen());
            memcpy(result
-> name(), name, nameLen);
            
break ;
        
default :
            result.reset(
new  UnknownAddress(name -> sa_family));
            MORDOR_ASSERT(nameLen 
<=  result -> nameLen());
            memcpy(result
-> name(), name, nameLen);
            
break ;
    }
    
return  result;
}

Address::ptr
Address::clone()
{
    
return  create(name(), nameLen());
}
复制代码

 

 显然,不足是基类的实现需要涉及所有存在的子类。而且,较真一点,时间效率也是问题。毕竟if else或者switch不如虚指针快。


你可能感兴趣的:(The clone pattern)