1

このコードを使用:

template <class T> class Test {
    T _temp;

public:
    Test() {
        std::cout << "Test()" << std::endl;
    };

    template <class T2> Test(Test<T2> const &test) {
        std::cout << "template <class T2> Test(Test<T2> const &test)" << std::endl;
    };

    template <class T2> Test(Test<T2> &&test) {
        std::cout << "template <class T2> Test(Test<T2> &&test)" << std::endl;
    };

};

このテストコードで:

Test<int> testInt;
Test<float> testFloat(testInt);
Test<float> testFloat2(std::move(testInt));

std::cout << "----------" << std::endl;

Test<int> testInt2;
Test<int> testInt3(testInt2);
Test<int> testInt4(std::move(testInt2));

この出力を生成します:

Test()
template <class T2> Test(Test<T2> const &test)
template <class T2> Test(Test<T2> &&test)
----------
Test()

同じ型を使用する場合は、変換コンストラクターの代わりに、既定のコピー コンストラクターと移動コンストラクターが使用されます。

しかし、クラスにデフォルトのコピーコンストラクターを追加すると:

Test(Test const &test) = default;

次の出力が生成されます。

Test()
template <class T2> Test(Test<T2> const &test)
template <class T2> Test(Test<T2> &&test)
----------
Test()
template <class T2> Test(Test<T2> &&test)

同じ型でも移動変換コンストラクタが呼び出されるのはなぜですか?

コードの重複を避けるために、コピーと変換コンストラクターを統合する方法はありますか?

4

1 に答える 1

2

暗黙的に生成された (= コンパイラによって生成された) 移動コンストラクターを追加するルールはかなり保守的です。安全な場合にのみ追加してください。コピー コンストラクターがユーザーによって定義されている場合、コンパイラーは、コンパイラーによって生成された単純な移動コンストラクターを追加しても安全であると想定できないため、移動コンストラクターは追加されません。実際には、Move コンストラクターの生成を防ぐために宣言するだけで十分です。IIRC はコピーのみの型を許可します。

コンストラクター テンプレートは、コピー コンストラクターまたはムーブ コンストラクターのいずれとも見なされないため、それらの暗黙的な生成を妨げません。

もちろん、ムーブ コンストラクターが宣言されていない場合は、代わりにオーバーロードの解決によって別のコンストラクターを選択できます。次の例を検討してください。

#include <iostream>
#include <utility>

struct loud
{
    loud() { std::cout << "default ctor\n"; }
    loud(loud const&) { std::cout << "copy ctor\n"; }
    loud(loud&&) { std::cout << "move ctor\n"; }
};

struct foo
{
    loud l;
};

struct bar
{
    loud l;
    bar() = default;
    bar(bar const&) = default;
};

int main()
{
    foo f0;
    foo f1(f0);
    foo f2(std::move(f0));

    std::cout << "--------------\n";

    bar b0;
    bar b1(b0);
    bar b2(std::move(b0));
}

出力:

デフォルトのセクター
コピー機
移動する
--------------
デフォルトのセクター
コピー機
コピー機

ここでfooは、暗黙的に宣言されたデフォルト、コピー、および移動コンストラクターをbar取得しますが、ユーザー宣言のコピー コンストラクターがあるため、暗黙的に宣言された移動コンストラクターは取得しません。


2 番目のケースでは、暗黙的に宣言された移動コンストラクターがないTest<int> testInt4(std::move(testInt2))ため、前者は cv 修飾されていない参照を取るため、コピー コンストラクターよりもコンストラクター テンプレートを優先するためのオーバーロード解決が行われます。


コードの重複を減らすために、構築を委任できます。

template <class T>
class Test {
    T _temp;

    struct tag {};

public:
    Test() {
        std::cout << "Test()" << std::endl;
    };

    Test(Test const& test)
        : Test(test, tag{})
    {}

    Test(Test&& test)
        : Test(std::move(test), tag{})
    {}

    template <class T2> Test(Test<T2> const &test, tag = {}) {
        std::cout << "template <class T2> Test(Test<T2> const &test)" << std::endl;
    };

    template <class T2> Test(Test<T2> &&test, tag = {}) {
        std::cout << "template <class T2> Test(Test<T2> &&test)" << std::endl;
    };

};

ところで、2 つのコンストラクター テンプレートの代わりに完全転送コンストラクター テンプレートを使用することで、いくつかのボイラープレートを削減することもできます。

この特性の使用:

template<class T>
struct is_Test : std::false_type {};
template<class T>
struct is_Test<Test<T>> : std::true_type {};

以下を定義できます。

    template <class T2,
              class = typename std::enable_if<is_Test<T2>::value>::type>
    Test(T2&& test, tag = {}) {
        std::cout << "template <class T2> Test(T2&& test)" << std::endl;
    };
于 2014-03-30T21:59:09.297 に答える