1

假设我有一个只能移动的类型。我们停止现有的默认提供的构造函数,但是右值引用引入了一种新的“风格”,我们可以将其用于签名的移动版本:

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

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

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

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

(这种情况就像将变量中保存的唯一指针传递给带有参数std::move的东西unique_ptr<foo> &&......但注意到调用站点的变量没有被清空。更改参数以unique_ptr<foo>修复它并且它被正确地清空,从而防止双重删除。:-/我没有孤立为什么这个似乎在其他地方工作时不好,但确凿证据是它第一次工作但不是随后的电话。)

我相信这是有充分理由的,你们中的许多人都可以明确地总结出来。与此同时,我开始像一个优秀的货物狂热程序员一样四处走动,去掉 &&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) {
        /* ... */
    }
};
4

1 回答 1

2

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

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

类的非模板构造函数X是复制构造函数,如果它的第一个参数的类型是X&、或,并且要么没有其他参数,要么所有其他参数都有默认参数const X&volatile X&const volatile X&

此外,脚注 122 说:

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

例子:

#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 和移动赋值运算符,它们可能会被隐式声明。如果你有一个用户声明的 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);调用了赋值运算符模板的特化,因为没有隐式声明的移动赋值运算符。

于 2013-11-02T21:01:27.827 回答