为什么 C++11 标准的早期草案中没有默认的移动赋值/移动构造函数?

发布于 2024-10-14 11:52:25 字数 497 浏览 4 评论 0原文

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

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

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

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

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

更新: 如果你看一下 GManNickG 的答案,他为此提供了一个很棒的宏。如果您不知道,如果您实现 move-semantics,则可以删除交换成员函数。

I'm a simple programmer. My class members variables most often consists of POD-types and STL-containers. Because of this I seldom have to write assignment operators or copy constructors, as these are implemented by default.

Add to this, if I use std::move on objects not movable, it utilizes the assignment-operator, meaning std::move is perfectly safe.

As I'm a simple programmer, I'd like to take advantage of the move-capabilities without adding a move constructor/assignment operator to every class I write, as the compiler could simply implemented them as "this->member1_ = std::move(other.member1_);..."

But it doesn't (at least not in Visual 2010), is there any particular reason for this?

More importantly; is there any way to get around this?

Update:
If you look down at GManNickG's answer he provides a great macro for this. And if you didn't know, if you implement move-semantics you can remove the swap member function.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

爱格式化 2024-10-21 11:52:25

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

有关该问题历史记录的更多信息,请参阅2010 年 WG21 论文列出 并搜索“mov”

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

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

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

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

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

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

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

12.8/22 中有类似的语言,指定何时将移动赋值运算符隐式声明为默认值。您可以在 N3203:收紧生成隐式移动的条件
,该决议主要基于 Bjarne Stroustrup 的论文 N3201:向右移动

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.

谷夏 2024-10-21 11:52:25

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

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

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

但这还不是故事的全部。可以声明一个ctor,但仍然定义为已删除:

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

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

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.

等你爱我 2024-10-21 11:52:25

(就目前而言,我正在研究一个愚蠢的宏......)

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

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

然后出现一个移动构造函数和移动赋值运算符。

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

(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.)

止于盛夏 2024-10-21 11:52:25

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文