模板是否应该为不同类型的参数制作非 Rvalue 引用构造函数/赋值
Should templates make non-Rvalue-reference constructors/assigns for move only parameters of different type?
假设我有一个仅移动类型。 我们停止默认提供的构造函数存在,但 Rvalue 引用引入了一种新的"风格",我们可以用于签名的移动版本:
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>
修复了它,并且它已正确为空,从而防止了双重删除。 :-/我没有隔离为什么这个在其他地方似乎有效时很糟糕,但吸烟枪是第一次工作,但随后的电话没有。
我相信这是有充分理由的,你们中的许多人都可以突出地总结一下。 与此同时,我开始像一个优秀的货物崇拜程序员一样四处走动,删除 &&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
类
X
的非模板构造函数是复制构造函数,如果它的第一个参数是X&
、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" ctorn";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template "move" assignment-opn";
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" ctorn";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template "move" assignment-opn";
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);
调用分配运算符模板的专用化,因为没有隐式声明的移动分配运算符。
- 如何将字节数组元素替换为修改的十六进制 ASCII 符号?
- 在Arduino中将字符串转换为(逗号分隔的十六进制)字符串到无符号字符数组
- 在 C++ 中使用带有十六进制十进制和八进制数的 unsetf
- 字符数组到十六进制字符串的转换 - 意外输出
- 将字符串转换为十六进制数组c++
- C++ 用户定义的数组被解释为十六进制?
- 十六进制 QString 表示为无符号字符数组
- 从十六进制字符串转换为十六进制字符数组
- 字符数组中的十六进制导致写入套接字时出现随机字符
- 将十六进制字符数组存储在字节数组中,而无需更改为 ASCII 或其他任何东西
- 如何使系统在十六进制数之前打印 0x,在八进制数之前打印 0?
- 以C++格式将十六进制字符串转换为十进制数
- 将大十六进制数转换为十进制形式(以 10 为基数的形式)的算法
- 从 std::数组创建十六进制代表
- 生成十六进制随机 16 字节数组
- 为什么将字符串输出到未命名的 std::ofstream 反而给了我一个十六进制数?
- 如何在C++中使用setiosflags打印出十进制数作为八进制数
- 将二进制读入十六进制字节数组
- 使用无符号字符数组输入十六进制数
- 一个从x数制到十进制的转换器