【问题标题】:Conversion constructor called for move but not for copy转换构造函数调用移动但不复制
【发布时间】:2014-05-10 02:42:45
【问题描述】:

使用此代码:

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)

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

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

【问题讨论】:

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


    【解决方案1】:

    添加隐式生成(= 编译器生成)移动构造函数的规则非常保守:仅在安全的情况下添加一个。如果一个拷贝构造函数已经被用户定义,编译器不能假设添加一些简单的编译器生成的移动构造函数仍然是安全的,所以不会添加移动构造函数。 其实,声明就足以阻止移动构造函数的生成,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));
    }
    

    输出:

    默认 ctor
    复制者
    移动 ctor
    --------------
    默认 ctor
    复制者
    复制ctor

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


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

    【讨论】:

    • 非常感谢,我需要研究一下完美的前向技巧,但是我还有一个问题,你说调用构造函数模板是因为编译器没有添加移动构造函数,因为有一个用户定义(声明)的复制构造函数。但是如果我删除了拷贝构造函数(=delete而不是=default),就会出现编译错误,构造函数模板不会被使用,为什么?
    • @Johnmph 嗯不知道你的意思。显式“删除”(通过使用= delete)函数实际上意味着此函数无效,如果有人试图调用它,则会引发错误(在编译时)。这并不意味着此功能不存在。因此,如果您显式删除复制构造函数,它仍然可以通过重载决议来选择。如果发生这种情况,编译器会抱怨。 (而且你不能像 move ctor 那样“使复制构造函数消失”。)
    • 谢谢我现在明白了,我有最后一个问题,你能告诉我你的标签结构允许委托构造的解决方案是否在编译代码中增加了一些开销,或者它是否只被编译器使用并且没有增加开销?
    • 对此我不能给你任何保证,但如果你使用优化编译,编译器应该能够删除tag参数:它未使用,为空并且该函数是在每个翻译单元中内联/定义的。
    猜你喜欢
    • 1970-01-01
    • 2022-11-21
    • 2015-06-10
    • 1970-01-01
    • 2017-07-28
    • 2022-01-12
    • 2012-06-28
    • 2014-01-24
    相关资源
    最近更新 更多