为什么没有默认的move-assignment/move-constructor?

Vik*_*ehr 87 c++ pod move-semantics c++11

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

除此之外,如果我使用std::move不可移动的对象,它使用赋值运算符,这意味着std::move非常安全.

由于我是一个简单的程序员,我想利用移动功能,而不需要为我编写的每个类添加移动构造函数/赋值运算符,因为编译器可以简单地将它们实现为" this->member1_ = std::move(other.member1_);..."

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

更重要的是; 有没有办法解决这个问题?

更新: 如果你低头看GManNickG的答案,他会为此提供一个很棒的宏.如果你不知道,如果你实现了移动语义,你可以删除交换成员函数.

Jam*_*lis 74

移动构造函数和赋值运算符的隐式生成一直存在争议,并且在最近的C++标准草案中进行了重大修订,因此目前可用的编译器在隐式生成方面可能表现不同.

有关该问题的更多信息,请参阅2010 WG21论文列表并搜索"mov"

目前的规范(N3225,从11月开始)规定(N3225 12.8/8):

如果类的定义X没有显式声明一个移动构造函数,那么当且仅当一个类的定义被隐式声明为默认值时

  • X 没有用户声明的复制构造函数,和

  • X 没有用户声明的复制赋值运算符,

  • X 没有用户声明的移动赋值运算符,

  • X 没有用户声明的析构函数,和

  • 移动构造函数不会被隐式定义为已删除.

12.8/22中有类似的语言,指定何时将移动赋值运算符隐式声明为默认值.您可以找到完整的更改列表,以支持N3203中隐式移动生成的当前规范:收紧生成隐式移动的条件 ,这主要基于Bjarne Stroustrup的论文N3201提出的一个解决方案:向右移动.

  • 我写了一篇小文章,其中有一些图表描述了隐式(移动)构造函数/赋值的关系:http://mmocny.wordpress.com/2010/12/09/implicit-move-wont-go/ (4认同)
  • @someguy:`virtual~D()= default;`应该工作. (3认同)
  • 也许我们可以在批准C++ 11后得到这个答案的更新?好奇什么行为赢了. (3认同)
  • @mmocny断开链接 (2认同)
  • @Guy Avraham:我想我所说的(已经 7 年了)是,如果我有一个用户声明的析构函数(甚至是一个空的虚拟析构函数),则不会将任何移动构造函数隐式声明为默认值。我想这会导致复制语义?(我已经很多年没有接触 C++了。)James McNellis 然后评论说`virtual ~D() = default;` 应该可以工作并且仍然允许隐式移动构造函数。 (2认同)

Jer*_*fin 12

隐式生成的移动构造函数已被考虑用于标准,但可能是危险的.见Dave Abrahams的分析.

但是,最后,标准确实包括隐式生成移动构造函数和移动赋值运算符,尽管有相当多的限制列表:

如果类X的定义没有显式声明一个移动构造函数,那么当且仅当
-X没有用户声明的复制构造函数时,将隐式声明一个默认值,
- X没有用户声明的复制赋值运算符,
- X没有用户声明的移动赋值运算符,
- X没有用户声明的析构函数,并且
- 移动构造函数不会被隐式定义为已删除.

但这并不是故事的全部内容.可以声明ctor,但仍定义为已删除:

隐式声明的复制/移动构造函数是其类的内联公共成员.如果X具有以下内容,则将类X的默认复制/移动构造函数定义为已删除(8.4.3):

- 具有非平凡对应构造函数的变体成员,X是类似联合的类,
- 类型为M(或其数组)的非静态数据成员,由于重载解析(13.3)而无法复制/移动,如应用于M的相应构造函数,导致歧义或从默认构造函数中删除或无法访问的函数,
- 由于重载解析(13.3)而无法复制/移动的直接或虚拟基类B,应用于B的相应构造函数,导致歧义或从默认构造函数中删除或无法访问的函数,
- 任何直接或虚拟基类或具有析构函数的非静态数据成员,该析构函数已从默认构造函数中删除或无法访问,
- 对于复制构造函数,rvalue引用类型的非静态数据成员,或
- 对于移动构造函数,是非静态数据成员或直接或虚拟基类,其类型不具有移动构造函数,并且 不可轻易复制.


GMa*_*ckG 8

(至于现在,我正在研究一个愚蠢的宏......)

是的,我也走了那条路.这是你的宏:

// 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
Run Code Online (Sandbox Code Playgroud)

// 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
Run Code Online (Sandbox Code Playgroud)

(我删除了真实的评论,这些评论是长度和纪录片.)

您可以将类中的基数和/或成员指定为预处理程序列表,例如:

#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;
};
Run Code Online (Sandbox Code Playgroud)

然后是一个移动构造函数和移动赋值运算符.

(顺便说一句,如果有人知道如何将细节组合成一个宏,那就会膨胀.)


Pup*_*ppy 5

VS2010 没有这样做,因为它们在实施时不是标准的。