c++ - 模板是否应该为仅移动不同类型的参数创建非右值引用构造函数/赋值?

标签 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) {
        /* ... */
    }
};

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

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

我敢肯定这是有充分理由的,你们中的许多人都可以清楚地总结出来。与此同时,我开始像一个好的 cargo-cult programmer 一样四处走动,删除了 &&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) {
        /* ... */
    }
};

最佳答案

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

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

A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments.

此外,脚注 122 说:

Because a template assignment operator or an assignment operator taking an rvalue reference parameter is never a copy assignment operator, the presence of such an assignment operator does not suppress the implicit declaration of a copy assignment operator. Such assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected, will be used to assign an object.

例子:

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

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


因此,如果您不声明非模板移动构造函数和移动赋值运算符,则可能会隐式声明它们。它们没有隐式声明,例如对于移动赋值操作,如果你有一个用户声明的 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); 调用赋值运算符模板的特化,因为没有隐式声明的移动赋值运算符。

关于c++ - 模板是否应该为仅移动不同类型的参数创建非右值引用构造函数/赋值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19746474/

相关文章:

c++ - 为什么可以将函数原型(prototype)放在函数内部?

c++ - boost::remove_pointer 是如何工作的?

c++ - 在 unsigned int 中使用第一位作为标志

c++ - 为什么大小为 2 的幂的数组速度较慢?为什么我会获得 -rdynamic 性能?

C++ - 函数总是返回真

c++ - OpenCV 中的 CV_8UC1 到 CV_32FC1 的转换

使用默认参数的 C++ 函数模板

c++ - 尝试在 C++ 中使用递归为模板化多项式类组合类似项

c++11 - c++11 operator[] 相当于 map 插入时的 emplace 吗?

c++ - 从模板中绑定(bind)模板函数