假设我有一个只能移动的类型。我们停止现有的默认提供的构造函数,但 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 typeX&
,const X&
,volatile X&
orconst 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/