2012-12-11 19 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; 
} 

私はDebianのテストシステム上のgcc 4.7を使用するコンパイラとしてあるコード、の表情を持っていることができます。コードと仮定すると名前のファイルに保存され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

コンパイラエラーから明らかなように、コンパイラは、T = B&のクラスHolderをインスタンス化しようとしています。つまり、クラスには参照型のメンバが格納され、コピーなどにいくつかの問題が発生します。

T&&(推論されたテンプレート引数のための)問題は普遍的な参照であり、すべてにバインドされるということにあります。 BのR値のためには、L値に対して、それが希望const Bリットル値のために(B&こととB&としてB& &&を解釈する崩壊基準を使用するTを推定し、r値の基準としてBとバインドことがTを推定しますTconst B&であると推測し、折りたたむ)。あなたの例では、bは変更可能なl値であり、T&&(これはB&であると推測される)をconst T&(より具体的にはconst B&と推定されます)と一致させるようにします。これは、const T&を取っているErasureのコンストラクタが実際には必要ではないことを意味します(がそのコンストラクタについて推測されていないため、Holderのものとは異なります)。

これに対する解決策は、ホルダークラスを作成するときに型から参照(constメンバーが必要な場合を除いてconst)を型から取り除くことです。また、std::moveの代わりにstd::forward<T>を使用する必要があります。これは、前述のようにコンストラクタも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クラス内の別のバグが、あります:あなたはあなたのHolder生のポインタでは、割り当てられたメモリをヒープへのストアが、それもカスタム処理を削除するには、どちらのカスタムデストラクタを持っていますコピー/移動/割当(3/5のルール)。これを解決するための1つの選択肢は、これらの操作を実装することです(または、=deleteを使用して不要なものを禁止する)ことができます。しかしこれはやや面倒なので私の個人的な提案はメモリを手動で管理するのではなく、メモリ管理にstd::unique_ptrを使用することです(あなたにコピー機能は与えませんが、最初にあなたを拡張する必要がある場合はHolderクラスのクローン作成いずれかの方法)。

その他の考慮点 Erasure::Holder<T>,AおよびBのカスタムコピー/移動コンストラクタを実装する理由は何ですか。デフォルトのものは完全に細かく、移動代入演算子の生成を無効にしません。

もう一つのポイントは、それが(T&&が良く一致両方const Erasure&Erasure&&あるÈrasure&に結合することができる)、コピー/移動コンストラクタと競合することにErasure(T &&o)が問題であるということです。これを避けるために、あなたはこれまであなたに似た何かを与え、Erasureの種類に対してチェックするenable_ifを使用することができます。

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修飾子が、補正はそれを行いません)。引数ostd::move()にしたくない場合は、std::move(o)を使用すると、実際にErasureのコンストラクタへの参照であるconstを渡すと致命的な結果になる可能性があります。

私はそこにいくつかの意味的なエラーを伝える限り、他のコードにはあまり注意を払っていませんでした(例えば、参照カウントのある形式か、含まれているポインタの形のclone()Erasureのリソースコントロール(コピーコンストラクタ、コピーアサイン、デストラクタなど)

関連する問題