c++中如何正確實現克隆(原型)模式?

看見大大們問題回答得很有價值,所以更改了問題。

————————————————

c++有拷貝構造函數。

Object *object1 = new Object();

Object *object2 = new Object(*object1);

在構造函數里實現深拷貝即可。

而網上的例子都是這樣的

Object *object1 = new Object();

Object *object2 = object1-&>clone();

請問網上例子這樣做有什麼好處?

如何正確實現克隆模式內容?

還想問,是否會因為clone函數是virtual的,就無法內聯而喪失一部分性能?

什麼時候virtual要被考慮性能問題呢?


Prototype 的意義在於,你拿到一個 Base* ,它指向某個 Derived 對象,你想克隆出 Derived 對象,但代碼中不寫出 Derived 的具體類型,因為有很多派生類,這種情況下你用構造函數是搞不定的,type-switch 是 bad smells 。

另外,這裡考慮 virtual 的性能損失是主次不分,構造對象需要分配內存,這開銷比一次虛函數調用大多了。

Prorobuf Message 就採用了 prototype 模式,你可以參考一下。


1)你的例子看起來沒什麼好處,一般情況下用拷貝構造函數即可。

2)上來就一通new,這代碼是c++的?我表示懷疑。

不過c++是需要Prototype的 ----- 如 @陳碩所言。舉例:

Derived d;
Base b = d;
Derived d2(b); // 試圖用b所指向的d來複制構造出一個新的d2,但這是不可以的,因為b的類型是Base,而不是Derived。

這種情況下,就需要 @孫明琦的前半部分代碼,然後就可以這樣:

Derived d2(*(b.clone())); // 由於clone是virtual的,因此這裡調用了Derived的clone返回了一個new出來的derived的對象的指針。

不過如他所言,現在的c++應該盡量避免raw pointer的使用,而應該使用智能指針。但是如果直接用智能指針是無法實現Prototype模式的(如他的後半部分代碼所示)。

可以這樣:

class Base
{
public:
std::unique_ptr& clone () { return std::make_unique&(*doClone()); }
private:
virtual Base* doClone() { return new Base(*this); }
};
class Derived : public Base
{
public:
std::unique_ptr& clone () { return std::make_unique&(*doClone()); }
private:
virtual Derived* doClone() { return new Derived(*this); }
};

注意,clone不是virtual的,而doClone是virtual和private的。使用這個class的人只能看到clone而看不到doClone,由此既實現了Prototype又避免了raw pointer的暴露。


現在已經不推薦拿裸指針亂搞了,而clone又沒法做出返回智能指針的實現,所以最好還是用拷貝構造函數+std::make_shared make_unique之類的東西來

class Base
{
public:
virtual Base* clone();
};

class Derived : public Base
{
public:
virtual Derived* clone() override; //這是可以的,Derived*和Base*協變
};

class Base
{
public:
virtual shared_ptr& clone();
};

class Derived : public Base
{
public:
virtual shared_ptr& clone() override; //這是不可以的,shared_ptr&和shared_ptr&不協變
};

當然如陳碩聚聚所說,當你不知道Base指向的是誰的時候,clone方法還是很有用的


#include &
#include &
#include &
#include &
using namespace std;

/** Record is the base Prototype */
class Record
{
public:
virtual ~Record() {}
virtual void print() = 0;
virtual unique_ptr& clone() = 0;
};

/** CarRecord is a Concrete Prototype */
class CarRecord : public Record
{
private:
string m_carName;
int m_ID;

public:
CarRecord(string carName, int ID) : m_carName(carName), m_ID(ID)
{
}

void print() override
{
cout &<&< "Car Record" &<&< endl &<&< "Name : " &<&< m_carName &<&< endl &<&< "Number: " &<&< m_ID &<&< endl &<&< endl; } unique_ptr& clone() override
{
return make_unique&(*this);
}
};

/** BikeRecord is the Concrete Prototype */
class BikeRecord : public Record
{
private:
string m_bikeName;
int m_ID;

public:
BikeRecord(string bikeName, int ID) : m_bikeName(bikeName), m_ID(ID)
{
}

void print() override
{
cout &<&< "Bike Record" &<&< endl &<&< "Name : " &<&< m_bikeName &<&< endl &<&< "Number: " &<&< m_ID &<&< endl &<&< endl; } unique_ptr& clone() override
{
return make_unique&(*this);
}
};

/** PersonRecord is the Concrete Prototype */
class PersonRecord : public Record
{
private:
string m_personName;
int m_age;

public:
PersonRecord(string personName, int age) : m_personName(personName), m_age(age)
{
}

void print() override
{
cout &<&< "Person Record" &<&< endl &<&< "Name : " &<&< m_personName &<&< endl &<&< "Age : " &<&< m_age &<&< endl &<&< endl; } unique_ptr& clone() override
{
return make_unique&(*this);
}
};

/** Opaque record type, avoids exposing concrete implementations */
enum RecordType
{
CAR,
BIKE,
PERSON
};

/** RecordFactory is the client */
class RecordFactory
{
private:
unordered_map&, hash& &> m_records;

public:
RecordFactory()
{
m_records[CAR] = make_unique&("Ferrari", 5050);
m_records[BIKE] = make_unique&("Yamaha", 2525);
m_records[PERSON] = make_unique&("Tom", 25);
}

unique_ptr& createRecord(RecordType recordType)
{
return m_records[recordType]-&>clone();
}
};

int main()
{
RecordFactory recordFactory;

auto record = recordFactory.createRecord(CAR);
record-&>print();

record = recordFactory.createRecord(BIKE);
record-&>print();

record = recordFactory.createRecord(PERSON);
record-&>print();
}


天吶這要搞死人了。為什麼要濫用特性。。。?直接用土逼方法不好嗎?

據陳先生所說,google protobuf的message類是一個抽象基類,有一個虛析構函數。並且實現了虛函數New(),相當於題主所說的clone()方法。

然後調用完New方法以後返回的是malloc出來的裸指針,用智能指針去接住它不就行了嘛


使用clone方式是能夠進行dynamic dispatch的,clone之後的對象其dynamic type可以和原對象相同.

而直接調用copy constructor就悲劇了,得到的只是一個dynamic type是原對象static type的對象,因為copy constructor不能為virtual.

當然大部分情況下各派生類的clone方法也不過是調用對應類的copy constructor.實現成別的倒也合法,但我目前想不到這樣做的可能理由.

使用clone方法倒是不可避免地需要侵入式設計.當然你也可以用一坨CRTP模板來節省代碼,不過下面的派生類是免不了要改繼承聲明的…

對了,我上面那幾段一句都沒提什麼虛函數的開銷之類的.原因很簡單:碰到不得不用dynamic type的場景,能只有虛函數的開銷算好的.很多時候要麼你得請出rtti要麼你就得寫一坨visitor跳來跳去,相比之下區區虛函數根本不算個事.更何況常見的一些實現中虛函數開銷真心毛毛雨.


一、原型模式的作用?

1、基本就是你需要從A的實例得到一份與A內容相同,但是又互不干擾的實例的話,就需要使用原型模式。

2、用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。這個其實和C++的拷貝構造函數的作用是相似的(但不相同),實際上就是動態抽取 當前對象 運行時 的 狀態。

3、當然有的時候,如果我們並不需要基於現有的對象複製新的對象,或者我們需要的就是一個乾淨的空對象,那麼我的首先還是工廠模式或者抽象工廠模式。

二、為什麼需要原型模式?

1、為什麼不用new直接新建對象,而要用原型模式?

首先,用new新建對象不能獲取當前對象運行時的狀態,其次就算new了新對象,在將當前對象的值複製給新對象,效率也不如原型模式高。

2、為什麼不直接使用拷貝構造函數,而要使用原型模式?

原型模式與拷貝構造函數是不同的概念,拷貝構造函數涉及的類是已知的,原型模式涉及的類可以是未知的(基類的拷貝構造函數只能複製得到基類的對象)。

原型模式生成的新對象可能是一個派生類。拷貝構造函數生成的新對象只能是類本身。原型模式是描述了一個通用方法(或概念),它不管是如何實現的,而拷貝構造則是描述了一個具體實現方法。

[cpp] view plain copy

  1. class base
  2. {
  3. public :
  4. base();
  5. base(base obj);
  6. virtual ~base();
  7. virtual base *clone() { return new base(*this) ; };
  8. };
  9. class derived : public base
  10. {
  11. public :
  12. derived();
  13. derived( derived );
  14. virtual base *clone(){return new derived (*this); }
  15. ....
  16. };
  17. base *obj1 = new base ;
  18. base *obj2 = new derived ;//基類指針指向派生類對象,怎樣用基類指針創建一個新的派生類對象?? 用基類的拷貝構造函數顯然不行。
  19. base *obj3 = obj1 .clone();
  20. base *obj4 = obj12.clone();

三、使用場景

1、資源優化場景

類初始化需要消化非常多的資源,這個資源包括數據、硬體資源等。

2、性能和安全要求的場景

通過new產生一個對象需要非常繁瑣的數據準備或訪問許可權,則可以使用原型模式。

3、一個對象多個修改者的場景

一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。

4、結合使用

在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone的方法創建一個對象,然後由工廠方法提供給調用者。原型模式已經與Java融為渾然一體,大家可以隨手拿來使用。

四、缺點

1、配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支持串列化的間接對象,或者引用含有循環結構的時候。

2、實現原型模式每個派生類都必須實現 Clone介面。

3、逃避構造函數的約束。

例子在http://blog.csdn.net/sinat_22991367/article/details/76595721


prototype類調用clone方法生成一個具體的類,被client調用,原型模式由於是直接在內存二進位流拷貝的,內存性能應該會相對較好。


推薦閱讀:

實現同一介面的不同類使用組合實現了多態,但是這破壞了DRY嗎?
設計模式在實際開發中用的多嗎?
設計模式之組合模式
設計模式之工廠模式
PyQt信號槽的Python實現

TAG:Java | C | 設計模式 |