Polymorphic cloning and the CRTP

許久沒有更新文章了~ 來分享一個最近學到的design pattern!

(全文參考:C++: Polymorphic cloning and the CRTP

在c++ coding 時,常會遇到一個問題如下:

class Vehicle {}
 
class Car : public Vehicle {}
class Plane : public Vehicle {}
class Train : public Vehicle {}

如果我想替這些人寫一個共用的 “clone(複製)” function 時該怎麼做呢?
我可以這樣寫:

Vehicle* make_a_copy(Vehicle *v)
{
  Vehicle *copy_of_vehicle = new ????(*v);
}

但這麼做會有個問題,???該填什麼? 他應該要隨著我要複製 Car Plane Train 等物件而跟著改變,而非寫死的。因此呢先有了第一種design pattern : polymorphic cloning pattern 我們直接來看code:

#include<iostream>
using namespace std;

class Vehicle
{
public:
    virtual ~Vehicle () {}
    virtual Vehicle *clone() const = 0;
    virtual void show()
    {
        cout<<"Vehicle show "<<endl;
    }   
};

class Car : public Vehicle
{
public:
    int a;
    virtual Car *clone() const { return new Car(*this); }
    virtual void show()
    {   
        cout<<"Car show"<<endl; 
    } 
}; 

int main() 
{ 
    Car* car_ptr = new Car(); car_ptr->a = 100;

    Car* car_ptr2 = car_ptr->clone();
    car_ptr2->show();
    cout<<"car_ptr2-> = "<<car_ptr2->a<<endl; 

    delete car_ptr; 
    car_ptr2->show();
    cout<<"car_ptr2->a = "<<car_ptr2->a<<endl;
       
}

result:

Car show
car_ptr2->a = 100
Car show
car_ptr2->a = 100

主要概念是每個物件要實現自己的clone function,且它主要是依靠 “copy constructor" 在運作。 code 解析如下:
1. class 中function 都有virtual 的前綴,表示希望等下可以支援 “多型”
2. function 結尾還有const = 0 表示要求繼承他的人要自己實作,否則compiler 會抱怨。
3. 一旦call 了 Car 的 clone 他會把 “自己丟到自己的copy constructor”,製造出一個自己的副本,並用一根pointer 指向它,並回傳該指標。
4. 為什麼可以用base class 型別的 pointer 去指向 derived class 的物件?這個一樣去參考 “多型” 的概念。

main function 裡頭做了一些測試,首先我先new 了一台car,並對member付值,接著我再create 一個car2 但是用clone的方式複製一台car 給car2,為了證明真的複製成功我把car2的member a 印出來看,真的有如期拿到100。 但會不會是個物件被兩個ptr 指著呢。為了測試我們把car_ptr 先給delete 掉,再一次的print 出car_ptr2 還是可以拿到預期得值。以上clone 的寫法就是polymorphic cloning pattern 。

接下來就是進階版的CRTP了:

所謂的CRTP指的是某個class繼承一個template class 且該teamplte 的type 又是自己,像下面這樣:

class SomeClass : public MyTemplateClass<SomeClass>

這跟我們今天的例子有甚麼關係呢?

假設我們覺得每個繼承Vehicle的人都樣實作clone很麻煩,能不能把clone function 給抽出來放到base class 呢? 當然是可以的,但須將原本的vehicle 給包一層皮,寫法如下:

class Vehicle
{
public:
    virtual ~Vehicle() {}
 
    virtual Vehicle *clone() const = 0;
 
    virtual void describe() const = 0;
};
 
template <typename Derived>
class VehicleCloneable : public Vehicle
{
public:
    virtual Vehicle *clone() const
    {
        return new Derived(static_cast<Derived const &>(*this));
    }
};

利用VehicleCloneable去繼承Vehicle 然後實現特別的clone function,值得注意的是:

static_cast<Derived const &>(*this)

這句話。如果我們沒有做轉型,this指標所指的type 是 “VehicleCloneable " ,會造成錯誤。

有了這樣的結構後car plane 等人就可以直接繼承VehicleCloneable 拉~

class Car : public VehicleCloneable<Car>
{
public:
    virtual void describe() const
    {
        std::cout << "I am a car" << std::endl;
    }
};
 
class Plane : public VehicleCloneable<Plane>
{
public:
    virtual void describe() const
    {
        std::cout << "I am a plane" << std::endl;
    }
};

clone 的方式跟上方的範例一樣,只不過clone 的實作改由vehicle 來實現。