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


89

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

これに加えて、std::move移動できないオブジェクトで使用する場合は、代入演算子を使用するため、std::move完全に安全です。

私は単純なプログラマーなので、コンパイラーが " this->member1_ = std::move(other.member1_);..." として実装するだけなので、作成するすべてのクラスに移動コンストラクター/割り当て演算子を追加せずに、移動機能を利用したいと思います。

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

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

更新: GManNickGの答えを見ると、GManNickGはこのための優れたマクロを提供しています。また、知らない場合は、移動セマンティクスを実装すれば、メンバーのスワップ関数を削除できます。


5
コンパイラーにデフォルトのmove
ctorを

3
std :: moveは移動を実行せず、単にl値からr値にキャストします。移動は引き続き移動コンストラクターによって実行されます。
オーウェンデラホイ

1
話してるのMyClass::MyClass(Myclass &&) = default;
Sandburg、

はい、今日は:)
Viktor Sehr

回答:


76

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

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

現在の仕様(11月からのN3225)には次のように記載されています(N3225 12.8 / 8)。

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

  • X ユーザーが宣言したコピーコンストラクターがない。

  • X ユーザーが宣言したコピー代入演算子はありません。

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

  • X ユーザーが宣言したデストラクタがない、および

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

12.8 / 22には同様の言語があり、移動割り当て演算子がデフォルトとして暗黙的に宣言される場合を指定しています。N3203の暗黙の移動生成の現在の仕様をサポートするために行われた変更の完全なリストを見つけることができます暗黙の移動を生成するための条件の強化は 、主にBjarne StroustrupのペーパーN3201:右に移動するによって提案された解決策の1つに基づいていました。


4
ここに暗黙の(移動)コンストラクター/割り当ての関係を説明するいくつかの図を含む小さな記事を書きました:mmocny.wordpress.com/2010/12/09/implicit-move-wont-go
mmocny

私はちょうど仮想としてそれを指定するために多型基底クラスで空白のデストラクタを定義する必要がありたびうわので、私は明示的にも移動コンストラクタと代入演算子を定義する必要があり:(。
someguy

@ジェームズ・マクネリス:これは私が以前試みたものですが、コンパイラーはそれを気に入らなかったようです。私はこの非常に返信でエラーメッセージを投稿するつもりでしたが、エラーを再現しようとした後、それがそれを言及していることに気付きましたcannot be defaulted *in the class body*。だから、私はデストラクタを外部で定義し、それはうまくいった:)。でも、ちょっと変だと思います。誰か説明がありますか?コンパイラはgcc 4.6.1
someguy

3
たぶん、C ++ 11が承認されたので、この回答の更新を取得できるでしょうか?どんな行動が勝ったのか好奇心が強い。
ジョセフガービン

2
@Guy Avraham:私が言っていた(7年経った)のは、ユーザーが宣言したデストラクタ(空の仮想デストラクタでも)がある場合、デフォルトとして移動コンストラクタが暗黙的に宣言されないことだと思います。私はそれがコピーのセマンティクスをもたらすと思いますか?(私は何年もC ++に触れていません。)James McNellis氏は、それvirtual ~D() = default;が機能し、暗黙のmoveコンストラクターを許可するようにコメントしました。
someguy

13

暗黙的に生成されたmoveコンストラクターが標準で考慮されていますが、危険な場合があります。Dave Abrahamsの分析を見る

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

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

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

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

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


現在のワーキングドラフトでは、特定の条件下で暗黙の移動生成が許可されており、この決議は主にアブラハムの懸念に対処していると思います。
James McNellis、2011年

Tweak 2とTweak 3の間の例では、どの動きがうまくいかないのか理解していません。説明できますか?
Matthieu M.

@Matthieu M .: Tweak 2とTweak 3の両方が壊れています。Tweak 2には、移動トラクターによって破壊される可能性のある不変式のプライベートメンバーがあります。Tweak 3では、クラス自体にプライベートメンバーはありませんが、プライベート継承を使用するため、ベースのパブリックメンバーと保護メンバーが派生のプライベートメンバーになり、同じ問題が発生します。
Jerry Coffin

1
moveコンストラクターがでクラスの不変式をどのように壊すのか、私には本当にわかりませんでしたTweak2Numberが移動され、vectorがコピーされるという事実と関係があると思いますが、よくわかりませんTweak3
Matthieu M.

あなたが与えたリンクは死んでいるようです?
ウルフ

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

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

そして、移動コンストラクタと移動割り当て演算子が出てきます。

(余談ですが、詳細を1つのマクロに組み合わせる方法を誰かが知っているとしたら、それはうねります。)


どうもありがとうございました。ただし、メンバー変数の数を引数として渡さなければならないことを除いて、私のものはかなり似ています(これは本当にうんざりです)。
Viktor Sehr、2011年

1
@Viktor:問題ありません。手遅れでない場合は、他の回答のいずれかを承認済みとしてマークする必要があります。鉱山はあなたの本当の質問への答えではなく、「ちなみに、ここに道があります」というものでした。
GManNickG 2011年

1
マクロを正しく読み取っている場合、コンパイラーがデフォルトのmoveメンバーを実装するとすぐに、上記の例はコピーできなくなります。明示的に宣言された移動メンバーが存在する場合、コピーメンバーの暗黙的な生成は禁止されます。
ハワードヒナン

@ハワード:それは大丈夫です、それはそれまでの一時的な解決策です。:)
GManNickG 2011年

GMan:スワップ機能がある場合、このマクロはmoveconstructor \ assignを追加します:
Viktor Sehr

4

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.