5

私は最近、型消去について学び始めました。このテクニックは私の人生を大きく簡素化できることがわかりました。したがって、私はこのパターンを実装しようとしました。ただし、型消去クラスのコピーおよび移動コンストラクターでいくつかの問題が発生します。それでは、最初にコードを見てみましょう。これは非常に簡単です。

#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テストシステムでgcc4.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);コンパイラーはまだmoveコンストラクターを使おうとしているようです。これはコンパイラの意図された動作ですか?型消去パターンの一般的な誤解はありますか?誰かがこれを正しくする方法を知っていますか?

4

2 に答える 2

3

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

問題は、T&&(推定されたテンプレート引数の場合)が普遍的な参照であるという事実にあります。つまり、すべてにバインドされます。それのr値の場合、Bそれはであると推定TBれ、r値参照としてバインドされます。l値の場合、それはであると推定Tされ、B&参照の折りたたみを使用して、次のように解釈B& &&されますB&const Bl値の場合、それはであると推定Tされconst B&、折りたたみを実行します) 。あなたの例bでは、変更可能なl値であり、コンストラクターが(と推定される)よりも(と推定される)より良い一致をとるようにT&&B&ます。これは、コンストラクターの取得が実際には必要ないことも意味します(const T&const B&Erasureconst T&HolderTそのコンストラクターについては推定されません)。

これに対する解決策は、ホルダークラスを作成するときに、型から参照(および、constメンバーが必要な場合を除いて、おそらくconstness)を取り除くことです。前述のように、コンストラクターもl値にバインドし、それらから移動することはおそらく悪い考えであるため、std::forward<T>の代わりに使用する必要があります。std::move

    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ヒープに割り当てられたメモリへのrawポインタにを格納しますが、それを削除するカスタムデストラクタも、コピー/移動/割り当てのカスタム処理もありません(ルールスリー/5)。これを解決するための1つのオプションは、これらの操作を実装することです(または、を使用して不要な操作を禁止します=delete)。ただし、これはやや面倒なので、私の個人的な提案は、メモリを手動で管理するのではなく、std::unique_ptrメモリ管理に使用することです(コピー機能は提供されませんが、必要に応じて、最初Holderにクローン作成のためにクラスを拡張する必要があります) 。

考慮すべきその他のポイント:、、およびのカスタムコピー/移動コンストラクErasure::Holder<T>ターを実装しているのはなぜですか?デフォルトのものは完全に問題なく、ムーブ代入演算子の生成を無効にすることはありません。AB

もう1つのポイントはErasure(T &&o)、コピー/移動コンストラクターと競合するという点で問題があります(どちらにT&&バインドできるかは、との両方よりも適切です)。これを回避するために、のタイプをチェックするために使用できます。これにより、次のようなものが得られます。Èrasure&const Erasure&Erasure&&enable_ifErasure

    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))
   {}
于 2012-12-11T21:32:20.327 に答える
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()含まれているポインターの形式、およびリソース制御が必要です) (つまり、コピーコンストラクタ、コピー割り当て、およびデストラクタ)Erasure

于 2012-12-11T21:32:27.717 に答える