2012-12-11 36 views
5

我最近開始瞭解類型擦除。事實證明,這種技術可以大大簡化我的生活。因此我試圖實現這種模式。但是,我遇到了類型擦除類的複製和移動構造函數的一些問題。 現在,讓我們先對代碼一看,這是相當直截了當使用模板複製和移動構造函數的C++類型擦除

#include<iostream> 
class A //first class 
{ 
    private: 
     double _value; 
    public: 
     //default constructor 
     A():_value(0) {} 
     //constructor 
     A(double v):_value(v) {} 
     //copy constructor 
     A(const A &o):_value(o._value) {} 
     //move constructor 
     A(A &&o):_value(o._value) { o._value = 0; } 

     double value() const { return _value; } 
}; 

class B //second class 
{ 
    private: 
     int _value; 
    public: 
     //default constructor 
     B():_value(0) {} 
     //constructor 
     B(int v):_value(v) {} 
     //copy constructor 
     B(const B &o):_value(o._value) {} 
     //move constructor 
     B(B &&o):_value(o._value) { o._value = 0; } 

     //some public member 
     int value() const { return _value; } 
}; 

class Erasure //the type erasure 
{ 
    private: 
     class Interface //interface of the holder 
     { 
      public: 
       virtual double value() const = 0; 
     }; 

     //holder template - implementing the interface 
     template<typename T> class Holder:public Interface 
     { 
      public: 
       T _object; 
      public: 
       //construct by copying o 
       Holder(const T &o):_object(o) {} 
       //construct by moving o 
       Holder(T &&o):_object(std::move(o)) {} 
       //copy constructor 
       Holder(const Holder<T> &o):_object(o._object) {} 
       //move constructor 
       Holder(Holder<T> &&o):_object(std::move(o._object)) {} 

       //implements the virtual member function 
       virtual double value() const 
       { 
        return double(_object.value()); 
       } 
     }; 

     Interface *_ptr; //pointer to holder 
    public: 
     //construction by copying o 
     template<typename T> Erasure(const T &o): 
      _ptr(new Holder<T>(o)) 
     {} 

     //construction by moving o 
     template<typename T> Erasure(T &&o): 
      _ptr(new Holder<T>(std::move(o))) 
     {} 

     //delegate 
     double value() const { return _ptr->value(); } 
}; 

int main(int argc,char **argv) 
{ 
    A a(100.2344); 
    B b(-100); 

    Erasure g1(std::move(a)); 
    Erasure g2(b); 

    return 0; 
} 

作爲一個編譯器我用gcc 4.7 Debian的測試系統上。假設代碼存儲在一個名爲terasure.cpp構建文件導致以下錯誤消息

$> g++ -std=c++0x -o terasure terasure.cpp 
terasure.cpp: In instantiation of ‘class Erasure::Holder<B&>’: 
terasure.cpp:78:45: required from ‘Erasure::Erasure(T&&) [with T = B&]’ 
terasure.cpp:92:17: required from here 
terasure.cpp:56:17: error: ‘Erasure::Holder<T>::Holder(T&&) [with T = B&]’ cannot be overloaded 
terasure.cpp:54:17: error: with ‘Erasure::Holder<T>::Holder(const T&) [with T = B&]’ 
terasure.cpp: In instantiation of ‘Erasure::Erasure(T&&) [with T = B&]’: 
terasure.cpp:92:17: required from here 
terasure.cpp:78:45: error: no matching function for call to ‘Erasure::Holder<B&>::Holder(std::remove_reference<B&>::type)’ 
terasure.cpp:78:45: note: candidates are: 
terasure.cpp:60:17: note: Erasure::Holder<T>::Holder(Erasure::Holder<T>&&) [with T = B&] 
terasure.cpp:60:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘Erasure::Holder<B&>&&’ 
terasure.cpp:58:17: note: Erasure::Holder<T>::Holder(const Erasure::Holder<T>&) [with T = B&] 
terasure.cpp:58:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘const Erasure::Holder<B&>&’ 
terasure.cpp:54:17: note: Erasure::Holder<T>::Holder(const T&) [with T = B&] 
terasure.cpp:54:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘B&’ 

看來,對於Erasure g2(b);編譯器還嘗試使用移動構造函數。這是編譯器的預期行爲嗎?我是否會誤解類型刪除模式中的某些內容?有人有一個想法如何得到這個權利?

+1

您在那裏拼錯了寶藏 –

回答

3

從編譯器錯誤中可以看出,編譯器試圖爲Holder類實例化T = B&。這意味着該類將存儲引用類型的成員,這會在複製等方面給您帶來一些問題。

問題在於T&&(對於推導出的模板參數)是一個通用參考,這意味着它將綁定到一切。對於B r值,將推斷TB並結合作爲r值的參考,對於1-值它將推斷TB&,並使用參考塌陷解釋B& &&作爲B&(對於const B升值它會推導出Tconst B&並進行摺疊)。在你的例子中,b是一個可修改的l值,使得構造函數T&&(推導爲B&)與const T&(推導爲const B&)的匹配更好。這也意味着構造函數Erasure並不是真的需要(Holder,因爲T不是爲該構造函數推導的)。

解決這個問題的方法是在創建持有者類時去除引用(並且可能是常量,除非您需要const成員)。你也應該使用std::forward<T>而不是std::move,因爲如前所述,構造函數也綁定到l值,並從這些移動可能是一個壞主意。

template<typename T> Erasure(T&& o): 
     _ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o)) 
    {} 

。在你的Erasure類另一個bug,這將不會被編譯器捕獲:您存儲Holder在原始指針堆分配的內存,但既沒有自定義的析構函數來刪除它,也沒有自定義處理複製/移動/分配(三/五規則)。解決這個問題的一個選擇是實施這些操作(或者禁止使用=delete的不重要的操作)。然而,這有些乏味,所以我個人的建議不是手動管理內存,而是使用內存管理的std::unique_ptr(不會給你複製能力,但如果你想要你首先需要擴展你的類Holder克隆無論如何)。

其他要考慮的要點: 爲什麼要爲Erasure::Holder<T>AB實施自定義複製/移動構造函數?默認的應該是非常好的,不會禁用移動賦值操作符的生成。

另一點是,Erasure(T &&o)是有問題的,因爲它會與複製/移動構造競爭(T&&可以綁定到Èrasure&這是一個更好的匹配,則既const Erasure&Erasure&&)。爲了避免這種情況,你可以使用enable_if對證類型的Erasure,給你類似這樣:

template<typename T, typename Dummy = typename std::enable_if<!std::is_same<Erasure, std::remove_reference<T>>::value>::type> 
    Erasure(T&& o): 
     _ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o)) 
    {} 
+0

對不起,我遲到的回覆並感謝您的建議,解決了問題。 –

1

你的問題是,類型T被推斷爲你構造以通用的參考基準。你想沿着這個線路使用的東西:

#include <type_traits> 

class Erasure { 
    .... 

    //construction by moving o 
    template<typename T> 
    Erasure(T &&o): 
     _ptr(new Holder<typename std::remove_reference<T>::type>(std::forward<T>(o))) 
    { 
    } 
}; 

也就是說,你需要刪除從T推導出任何引用(也許還任CV預選賽,但修正沒有做到這一點)。然後你不想std::move()參數o但是std::forward<T>()它:使用std::move(o)可能會產生災難性的後果,如果你實際上通過非const引用的構造函數Erasure

我並沒有太注意其他代碼放在哪裏,據我可以告訴那裏也有一些語義錯誤(例如,你需要某種形式的引用計數或包含的指針形式的clone() int,以及Erasure中的資源控制(即複製構造函數,複製分配和析構函數)

相關問題