【问题标题】:Why no default move-assignment/move-constructor?为什么没有默认的移动分配/移动构造函数?
【发布时间】:2011-06-16 17:41:21
【问题描述】:

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

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

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

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

更重要的是; 有什么办法可以解决这个问题吗?

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

【问题讨论】:

  • 你知道你可以让编译器生成一个默认的移动 ctor
  • std::move 不执行移动,它只是从左值转换为右值。移动仍然由移动构造函数执行。
  • 你在说MyClass::MyClass(Myclass &&) = default;吗?
  • 是的,现在:)

标签: c++ c++11 move-semantics


【解决方案1】:

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

有关问题历史的更多信息,请参阅the 2010 WG21 papers list 并搜索“mov”

当前规范(N3225,11 月起)规定 (N3225 12.8/8):

如果X 类的定义没有显式声明移动构造函数,当且仅当

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

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

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

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

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

12.8/22 中有类似的语言指定何时将移动赋值运算符隐式声明为默认值。您可以在 N3203: Tightening the conditions for generating implicit moves 中找到为支持当前隐式移动生成规范所做的更改的完整列表,该规范主要基于 Bjarne Stroustrup 的论文 N3201: Moving right along 提出的解决方案之一。

【讨论】:

  • 我写了一篇小文章,其中有一些描述隐式(移动)构造函数/赋值关系的图表:mmocny.wordpress.com/2010/12/09/implicit-move-wont-go
  • 呃,所以每当我必须在多态基类中定义空白析构函数只是为了将其指定为虚拟时,我也必须显式定义移动构造函数和赋值运算符:(。
  • @James McNellis:这是我之前尝试过的,但编译器似乎不喜欢它。我打算在这个回复中发布错误消息,但在尝试重现错误之后,我意识到它提到了它cannot be defaulted *in the class body*。所以,我在外面定义了析构函数,它起作用了:)。不过我觉得有点奇怪。有人有解释吗?编译器是 gcc 4.6.1
  • 也许我们可以在 C++11 得到批准后得到这个答案的更新?好奇什么行为会赢。
  • @Guy Avraham:我想我所说的(已经 7 年了)是,如果我有一个用户声明的析构函数(甚至是一个空的虚拟析构函数),则不会将移动构造函数隐式声明为默认。我想这会导致复制语义? (我已经很多年没有接触过 C++了。)James McNellis 然后评论说virtual ~D() = default; 应该可以工作并且仍然允许隐式移动构造函数。
【解决方案2】:

隐式生成的移动构造函数已被考虑用于标准,但可能很危险。请参阅 Dave Abrahams 的 analysis

然而,最终,该标准确实包括隐式生成移动构造函数和移动赋值运算符,尽管有相当多的限制:

如果类 X 的定义没有显式声明移动构造函数,则当且仅当
— X 没有用户声明的复制构造函数,
— X 没有用户声明的复制赋值运算符,
— X 没有用户声明的移动赋值运算符,
— X 没有用户声明的析构函数,并且
— 移动构造函数不会被隐式定义为已删除。

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

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

— 具有非平凡对应构造函数的变体成员,并且 X 是类联合类,
— 类类型 M(或其数组)的非静态数据成员,不能复制/移动,因为重载决议 (13.3) 应用于 M 的相应构造函数,导致歧义或函数从默认构造函数,
— 无法复制/移动的直接或虚拟基类 B,因为应用于 B 的相应构造函数的重载决议 (13.3) 会导致歧义或从默认构造函数中删除或无法访问的函数,
— 具有从默认构造函数中删除或不可访问的析构函数的类型的任何直接或虚拟基类或非静态数据成员,
— 对于复制构造函数,右值引用类型的非静态数据成员,或
— 对于移动构造函数,非静态数据成员或直接或虚拟基类,其类型没有移动构造函数且不可轻易复制。

【讨论】:

  • 当前的工作草案确实允许在某些条件下进行隐式移动生成,我认为该决议在很大程度上解决了亚伯拉罕斯的担忧。
  • 我不确定我是否理解在 Tweak 2 和 Tweak 3 之间的示例中可能会中断什么动作。你能解释一下吗?
  • @Matthieu M.:Tweak 2 和 Tweak 3 都被破坏了,而且方式非常相似,真的。在 Tweak 2 中,有一些具有不变量的私有成员可以被移动 ctor 破坏。在 Tweak 3 中,类自身没有私有成员,但由于它使用私有继承,基类的公共和受保护成员成为派生的私有成员,导致同样的问题。
  • 我真的不明白移动构造函数如何破坏Tweak2 中的类不变量。我想这与Number 将被移动并且vector 将被复制这一事实有关......但我不确定:/我确实理解问题会级联到Tweak3。跨度>
  • 你给的链接好像失效了?
【解决方案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

(我已经删除了真正的cmets,它们是长度和纪录片。)

您将类中的基类和/或成员指定为预处理器列表,例如:

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

还有一个移动构造函数和移动赋值运算符。

(顺便说一句,如果有人知道我如何将细节组合到一个宏中,那就太好了。)

【讨论】:

  • 非常感谢,我的非常相似,只是我必须将成员变量的数量作为参数传递(这真的很糟糕)。
  • @Viktor:没问题。如果还不算太晚,我认为您应该将其他答案之一标记为已接受。我的更多是“顺便说一句,这是一种方式”,而不是您真正问题的答案。
  • 如果我正确地阅读了你的宏,那么一旦你的编译器实现了默认的移动成员,你上面的例子将变得不可复制。当存在显式声明的移动成员时,将禁止隐式生成复制成员。
  • @Howard:没关系,在那之前这是一个临时解决方案。 :)
  • GMan:如果你有交换函数,这个宏会添加 moveconstructor\assign:
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 2020-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-07
    • 1970-01-01
    • 2020-05-08
    • 2022-01-09
    • 2017-01-10
    相关资源
    最近更新 更多