转换构造函数调用move而不是copy

时间:2014-03-30 21:25:00

标签: c++ templates constructor copy-constructor coercion

使用此代码:

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)

即使使用相同的类型,也会调用移动转换构造函数,为什么?

有没有办法统一复制和转换构造函数以避免重复代码?

1 个答案:

答案 0 :(得分:2)

添加隐式生成(=编译器生成的)移动构造函数的规则非常保守:只有在安全的情况下才添加一个。如果用户已经定义了复制构造函数,则编译器不能假设添加一些简单的编译器生成的移动构造函数仍然是安全的,因此不会添加移动构造函数。 实际上,它足以声明它以防止生成移动构造函数,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));
}

输出:

default ctor
copy ctor
move ctor
--------------
default ctor
copy ctor
copy ctor

这里,foo将隐式声明默认,复制和移动构造函数,而bar不会获得隐式声明的移动构造函数,因为它具有用户声明的复制构造函数


在你的第二种情况下,没有隐式声明的移动构造函数,因此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;
    };

};

顺便说一句,您还可以使用完美的转发构造函数模板而不是两个构造函数模板来减少一些样板:

使用此特性:

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;
    };