模板是否应使非Rvalue-reference构造函数/赋值仅用于移动不同类型的参数?

时间:2013-11-02 20:23:05

标签: c++ templates c++11 rvalue-reference

让我们说我有一个只移动的类型。我们停止现有的默认提供的构造函数,但Rvalue引用引入了一个新的" flavor"我们可以用于签名的移动版本:

class CantCopyMe
{
private:
    CantCopyMe (CantCopyMe const & other) = delete;
    CantCopyMe & operator= (CantCopyMe const & other) = delete;

public:
    CantCopyMe (CantCopyMe && other) {
        /* ... */
    }

    CantCopyMe & operator= (CantCopyMe && other) {
        /* ... */
    }
};

我最近认为你总是应该通过Rvalue引用传递可移动类型。现在它看起来只有非常特殊的情况需要这样做......就像这两个。如果你把它们放在任何地方,事情似乎大部分时间都有效,但我刚发现一个编译器没有运行转移所有权的代码部分。

(这是一种情况,例如将带有std::move的变量中保存的唯一指针传递给采用unique_ptr<foo> &&参数的内容...但注意到调用点上的变量没有被取消将参数更改为unique_ptr<foo>修正了它并且它已被正确排除,从而防止了双重删除。: - /我没有理解为什么这个看起来很糟糕在其他地方工作,但吸烟枪是第一次工作但不是随后的电话。)

我确定这是一个很好的理由,你们中的许多人都可以得到充分的总结。与此同时,我开始像一个好的cargo-cult programmer去除&amp;&amp; s。

但是,如果你正在写一个模板化的课程,它看起来像这样呢?

template <class FooType>
class CantCopyMe
{
private:
    CantCopyMe (CantCopyMe const & other) = delete;
    CantCopyMe & operator= (CantCopyMe const & other) = delete;

public:
    template<class OtherFooType>
    CantCopyMe (CantCopyMe<OtherFooType> && other) {
        /* ... */
    }

    template<class OtherFooType>
    CantCopyMe & operator= (CantCopyMe<OtherFooType> && other) {
        /* ... */
    }
};

由于某些原因这是不好的做法,当OtherFooType和FooType不相同时你应该单独分解......然后它只是按值传递?

template <class FooType>
class CantCopyMe
{
private:
    CantCopyMe (CantCopyMe const & other) = delete;
    CantCopyMe & operator= (CantCopyMe const & other) = delete;

public:
    CantCopyMe (CantCopyMe && other) {
        /* ... */
    }

    CantCopyMe & operator= (CantCopyMe && other) {
        /* ... */
    }

    template<class OtherFooType>
    CantCopyMe (CantCopyMe<OtherFooType> other) {
        /* ... */
    }

    template<class OtherFooType>
    CantCopyMe & operator= (CantCopyMe<OtherFooType> other) {
        /* ... */
    }
};

1 个答案:

答案 0 :(得分:2)

我认为可能出乎意料的原因有一个简单的答案:

复制/移动构造函数或赋值运算符从不模板(特化)。例如。 [class.copy] / 2

  

如果第一个参数的类型为XX&const X&volatile X&,则类const volatile X&的非模板构造函数是一个复制构造函数,并且没有其他参数,或者所有其他参数都有默认参数。

此外,脚注122说:

  

因为模板赋值运算符或赋予rvalue引用参数的赋值运算符永远不是复制赋值运算符,所以这种赋值运算符的存在不会抑制复制赋值运算符的隐式声明。此类赋值运算符与其他赋值运算符(包括复制赋值运算符)一起参与重载解析,如果选择,则将用于分配对象。

示例:

#include <iostream>
#include <utility>

template<class T>
struct X
{
    X() {}

    template<class U>
    X(X<U>&&)
    {
        std::cout << "template \"move\" ctor\n";
    }

    template<class U>
    X& operator= (X<U>&&)
    {
        std::cout << "template \"move\" assignment-op\n";
        return *this;
    }
};

int main()
{
    X<int> x;                     // no output
    X<int> y(x);                  // no output
    y = std::move(x);             // no output
    X<double> z( std::move(x) );  // output
    y = std::move(z);             // output
}

在此示例中,使用了隐式声明的移动构造函数和移动赋值运算符。


因此,如果您未声明非模板移动ctor并移动赋值运算符,则可能会隐式声明它们。它们没有被隐含地声明,例如对于move assignment-op,如果你有一个用户声明的dtor;有关详细信息,请参阅[class.copy] / 11和[class.copy] / 20。

示例:在上面的示例中添加dtor:

#include <iostream>
#include <utility>

template<class T>
struct X
{
    X() {}
    ~X() {}

    template<class U>
    X(X<U>&&)
    {
        std::cout << "template \"move\" ctor\n";
    }

    template<class U>
    X& operator= (X<U>&&)
    {
        std::cout << "template \"move\" assignment-op\n";
        return *this;
    }
};

int main()
{
    X<int> x;                     // no output
    X<int> y(x);                  // no output
    y = std::move(x);             // output
    X<double> z( std::move(x) );  // output
    y = std::move(z);             // output
}

这里,第一个移动赋值y = std::move(x);调用赋值运算符模板的特化,因为没有隐式声明的移动赋值运算符。