为什么没有默认的 move-赋值/move-构造函数?

我只是个简单的程序员。我的类成员变量通常由 POD 类型和 STL 容器组成。因此,我很少需要编写赋值运算符或复制构造函数,因为它们是默认实现的。

此外,如果我对不可移动的对象使用 std::move,它会使用赋值操作符,这意味着 std::move是完全安全的。

因为我是一个简单的程序员,所以我希望能够利用 move 功能,而不需要在我编写的每个类中添加 move 构造函数/赋值操作符,因为编译器可以简单地将它们实现为“ this->member1_ = std::move(other.member1_);...

但是它没有(至少在 Visual2010中没有) ,有什么特别的原因吗?

更重要的是 有什么办法能避开这个吗?

更新: 如果你低头看 GManNickG 的答案,他提供了一个伟大的宏观这一点。如果您不知道,如果您实现 move 语义,您可以删除交换成员函数。

45936 次浏览

Implicitly generated move constructors have been considered for the standard, but can be dangerous. See Dave Abrahams's analysis.

In the end, however, the standard did include implicit generation of move constructors and move assignment operators, though with a fairly substantial list of limitations:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
— X does not have a user-declared copy constructor,
— X does not have a user-declared copy assignment operator,
— X does not have a user-declared move assignment operator,
— X does not have a user-declared destructor, and
— the move constructor would not be implicitly defined as deleted.

That's not quite all there is to the story though. A ctor can be declared, but still defined as deleted:

An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/move constructor for a class X is defined as deleted (8.4.3) if X has:

— a variant member with a non-trivial corresponding constructor and X is a union-like class,
— a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3), as applied to M’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
— a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3), as applied to B’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
— any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor,
— for the copy constructor, a non-static data member of rvalue reference type, or
— for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.

VS2010 doesn't do it because they weren't Standard at the time of implementation.

The implicit generation of move constructors and assignment operators has been contentious and there have been major revisions in recent drafts of the C++ Standard, so currently available compilers will likely behave differently with respect to implicit generation.

For more about the history of the issue, see the 2010 WG21 papers list and search for "mov"

The current specification (N3225, from November) states (N3225 12.8/8):

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor, and

  • X does not have a user-declared copy assignment operator,

  • X does not have a user-declared move assignment operator,

  • X does not have a user-declared destructor, and

  • the move constructor would not be implicitly defined as deleted.

There is similar language in 12.8/22 specifying when the move assignment operator is implicitly declared as defaulted. You can find the complete list of changes made to support the current specification of implicit move generation in N3203: Tightening the conditions for generating implicit moves , which was based largely on one of the resolutions proposed by Bjarne Stroustrup's paper N3201: Moving right along.

(as for now, I'm working on a stupid macro...)

Yeah, I went that route too. Here's your macro:

// 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

(I've removed the real comments, which are length and documentary.)

You specify the bases and/or members in your class as a preprocessor list, for example:

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

And out comes a move-constructor and move-assignment operator.

(As an aside, if anyone knows how I could combine the details into one macro, that would be swell.)