[英]Why no default move-assignment/move-constructor?
我是一个简单的程序员。 我的类成员变量通常由 POD 类型和 STL 容器组成。 因此,我很少需要编写赋值运算符或复制构造函数,因为它们是默认实现的。
std::move
,如果我在不可移动的对象上使用std::move
,它会使用赋值运算符,这意味着std::move
是完全安全的。
由于我是一个简单的程序员,我想利用移动功能的优势,而无需向我编写的每个类添加移动构造函数/赋值运算符,因为编译器可以简单地将它们实现为“ this->member1_ = std::move(other.member1_);...
"
但它没有(至少在 Visual 2010 中没有),这有什么特别的原因吗?
更重要的是; 有什么办法可以解决这个问题吗?
更新:如果你看不起 GManNickG 的回答,他为此提供了一个很棒的宏。 如果您不知道,如果您实现移动语义,您可以删除交换成员函数。
移动构造函数和赋值运算符的隐式生成一直存在争议,并且在 C++ 标准的最新草案中进行了重大修订,因此当前可用的编译器在隐式生成方面的行为可能会有所不同。
有关该问题历史的更多信息,请参阅2010 WG21 论文列表并搜索“mov”
当前规范(N3225,来自 11 月)规定 (N3225 12.8/8):
如果类
X
的定义没有显式声明移动构造函数,则当且仅当
X
没有用户声明的复制构造函数,并且
X
没有用户声明的复制赋值运算符,
X
没有用户声明的移动赋值运算符,
X
没有用户声明的析构函数,并且移动构造函数不会被隐式定义为已删除。
12.8/22 中有类似的语言指定何时将移动赋值运算符隐式声明为默认值。 您可以在N3203 中找到为支持当前隐式移动生成规范所做的更改的完整列表:收紧生成隐式移动的条件,这主要基于 Bjarne Stroustrup 的论文N3201:向右移动提出的决议之一。
隐式生成的移动构造函数已被考虑用于标准,但可能是危险的。 请参阅 Dave Abrahams 的分析。
然而,最终,该标准确实包含了移动构造函数和移动赋值运算符的隐式生成,尽管有相当多的限制:
如果类 X 的定义没有显式声明移动构造函数,则当且仅当
— X 没有用户声明的复制构造函数,
— X 没有用户声明的复制赋值运算符,
— X 没有用户声明的移动赋值运算符,
— X 没有用户声明的析构函数,并且
— 移动构造函数不会被隐式定义为已删除。
但这还不是故事的全部。 可以声明一个ctor,但仍定义为已删除:
隐式声明的复制/移动构造函数是其类的内联公共成员。 如果 X 具有:
— 具有非平凡对应构造函数的变体成员,X 是类似联合的类,
— 不能复制/移动的类类型 M(或其数组)的非静态数据成员,因为应用于 M 的相应构造函数的重载决议 (13.3) 导致歧义或函数被删除或无法从默认构造函数,
— 无法复制/移动的直接或虚拟基类 B,因为应用于 B 的相应构造函数的重载决议 (13.3) 会导致歧义或从默认构造函数中删除或无法访问的函数,
— 具有从默认构造函数中删除或不可访问的析构函数的类型的任何直接或虚拟基类或非静态数据成员,
— 对于复制构造函数,右值引用类型的非静态数据成员,或
— 对于移动构造函数,非静态数据成员或直接或虚拟基类,其类型没有移动构造函数且不可简单复制。
(至于现在,我正在研究一个愚蠢的宏......)
是的,我也走这条路。 这是你的宏:
// detail/move_default.hpp
#ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP
#define UTILITY_DETAIL_MOVE_DEFAULT_HPP
#include <boost/preprocessor.hpp>
#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE(pR, pData, pBase) pBase(std::move(pOther))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE(pR, pData, pBase) pBase::operator=(std::move(pOther));
#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR(pR, pData, pMember) pMember(std::move(pOther.pMember))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT(pR, pData, pMember) pMember = std::move(pOther.pMember);
#define UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers) \
pT(pT&& pOther) : \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM( \
UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases)) \
, \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM( \
UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers)) \
{} \
\
pT& operator=(pT&& pOther) \
{ \
BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases) \
BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers) \
\
return *this; \
}
#define UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases) \
pT(pT&& pOther) : \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM( \
UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases)) \
{} \
\
pT& operator=(pT&& pOther) \
{ \
BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases) \
\
return *this; \
}
#define UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers) \
pT(pT&& pOther) : \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM( \
UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers)) \
{} \
\
pT& operator=(pT&& pOther) \
{ \
BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers) \
\
return *this; \
}
#endif
// move_default.hpp
#ifndef UTILITY_MOVE_DEFAULT_HPP
#define UTILITY_MOVE_DEFAULT_HPP
#include "utility/detail/move_default.hpp"
// move bases and members
#define UTILITY_MOVE_DEFAULT(pT, pBases, pMembers) UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)
// base only version
#define UTILITY_MOVE_DEFAULT_BASES(pT, pBases) UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)
// member only version
#define UTILITY_MOVE_DEFAULT_MEMBERS(pT, pMembers) UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)
#endif
(我已经删除了真正的评论,即长度和纪录片。)
您将类中的基类和/或成员指定为预处理器列表,例如:
#include "move_default.hpp"
struct foo
{
UTILITY_MOVE_DEFAULT_MEMBERS(foo, (x)(str));
int x;
std::string str;
};
struct bar : foo, baz
{
UTILITY_MOVE_DEFAULT_BASES(bar, (foo)(baz));
};
struct baz : bar
{
UTILITY_MOVE_DEFAULT(baz, (bar), (ptr));
void* ptr;
};
然后是移动构造函数和移动赋值运算符。
(顺便说一句,如果有人知道我如何将细节组合成一个宏,那就太好了。)
VS2010 没有这样做,因为它们在实施时不是标准的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.