web-dev-qa-db-ja.com

デフォルトのmove-assignment / move-constructorがないのはなぜですか?

私はシンプルなプログラマーです。私のクラスメンバー変数は、ほとんどの場合、PODタイプとSTLコンテナーで構成されています。このため、デフォルトで実装されているため、代入演算子やコピーコンストラクターを記述する必要はほとんどありません。

さらに、移動できないオブジェクトで_std::move_を使用する場合、割り当て演算子を使用します。つまり、_std::move_は完全に安全です。

私はシンプルなプログラマーなので、コンパイラが「this->member1_ = std::move(other.member1_);...

しかし、そうではありません(少なくともVisual 2010ではそうではありません)。これには特別な理由はありますか?

さらに重要なことには; これを回避する方法はありますか?

更新:GManNickGの答えを見れば、このための素晴らしいマクロを提供してくれます。わからない場合は、move-semanticsを実装すると、swapメンバー関数を削除できます。

87
Viktor Sehr

移動コンストラクタと代入演算子の暗黙的な生成は論争を呼んでおり、C++標準の最近のドラフトでは大幅な改訂が行われているため、現在利用可能なコンパイラは、暗黙的な生成に関して異なる動作をする可能性があります。

この問題の歴史の詳細については、 2010 WG21論文リスト を参照し、「mov」を検索してください

現在の仕様(N3225、11月)では次のように述べています(N3225 12.8/8):

クラスXの定義が移動コンストラクターを明示的に宣言していない場合、次の場合にのみデフォルトとして暗黙的に宣言されます。

  • Xにはユーザーが宣言したコピーコンストラクターがありません。

  • Xには、ユーザーが宣言したコピー割り当て演算子がありません。

  • Xには、ユーザーが宣言した移動代入演算子がありません。

  • Xにはユーザーが宣言したデストラクタがありません。

  • 移動コンストラクターは、暗黙的に削除済みとして定義されません。

12.8/22には、移動割り当て演算子がデフォルトとして暗黙的に宣言されるタイミングを指定する同様の言語があります。 N3203:暗黙的な移動を生成するための条件を厳しくする で、暗黙的な移動生成の現在の仕様をサポートするために加えられた変更の完全なリストを見つけることができます。 N3201:右に移動

74
James McNellis

暗黙的に生成された移動コンストラクターは標準として考慮されていますが、危険な場合があります。 Dave Abrahamsの analysis をご覧ください。

ただし、最終的に、標準には移動コンストラクタと移動代入演算子の暗黙的な生成が含まれますが、かなり多くの制限があります。

クラスXの定義が移動コンストラクターを明示的に宣言していない場合は、次の場合にのみデフォルトとして暗黙的に宣言されます。
— Xにはユーザーが宣言したコピーコンストラクターがありません。
— Xには、ユーザーが宣言したコピー割り当て演算子がありません。
— Xには、ユーザーが宣言した移動割り当て演算子がありません。
— Xには、ユーザーが宣言したデストラクタがありません。
—移動コンストラクターは暗黙的に削除済みとして定義されません。

しかし、それだけではありません。 ctorは宣言できますが、削除済みとして定義されます:

暗黙的に宣言されたコピー/移動コンストラクターは、そのクラスのインラインパブリックメンバーです。クラスXのデフォルトのコピー/移動コンストラクターは、Xが次の場合に削除済み(8.4.3)として定義されます。

—自明ではない対応するコンストラクターを持ち、Xがユニオンのようなクラスであるバリアントメンバー
—クラスタイプM(またはその配列)の非静的データメンバー。これは、Mの対応するコンストラクターに適用されるオーバーロード解決(13.3)により、あいまいさまたは関数になるため、コピー/移動できません。デフォルトのコンストラクタから削除されたかアクセスできない
—オーバーロード解決(13.3)がBの対応するコンストラクターに適用されると、コピーまたは移動できない直接または仮想ベースクラスBが、曖昧さまたはデフォルトのコンストラクターから削除またはアクセスできない関数になるため、
—削除された、またはデフォルトのコンストラクターからアクセスできないデストラクタを持つ型の任意の直接または仮想ベースクラスまたは非静的データメンバ、
—コピーコンストラクターの場合、右辺値参照型の非静的データメンバー、または
—移動コンストラクターの場合、非静的データメンバー、または移動コンストラクターを持たず、簡単にコピーできない型を持つ直接または仮想基本クラス。

12
Jerry Coffin

(今のところ、私は愚かなマクロに取り組んでいます...)

ええ、私もそのルートに行きました。マクロは次のとおりです。

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

そして、move-constructorとmove-assignmentオペレーターが出てきます。

(余談ですが、詳細を1つのマクロに結合する方法を誰かが知っていれば、それは大きくなります。)

8
GManNickG

VS2010は、実装時に標準ではなかったため、実行しません。

4
Puppy