C ++ 11で型を移動不可能にするのはいつですか?


127

これが検索結果に表示されないことに驚いたのですが、C ++ 11での移動のセマンティクスの有用性を考えると、誰かが以前にこれを尋ねたのではないかと思いました。

C ++ 11でクラスを移動不可能にする必要があるのはいつですか(または私にとっては良い考えですか)?

(理由他の既存のコードとの互換性の問題より。)


2
「タイプを移動するには高価」( -ブーストは、常に一歩先ですboost.org/doc/libs/1_48_0/doc/html/container/move_emplace.html
SChepurin

1
これは(+1私からの)非常に優れた有用な質問だと思います。ハーブ(または彼の双子のようです)からの非常に徹底的な回答があったので、FAQエントリにしました。誰かがラウンジで私にpingを送信することに異議を唱えた場合、これについてそこで議論することができます。
sbi

1
AFAIKの移動可能なクラスは引き続きスライスされる可能性があるため、すべての多態的な基本クラス(つまり、仮想関数を持つすべての基本クラス)の移動(およびコピー)を禁止することは理にかなっています。
フィリップ

1
@Mehrdad:「Tは移動コンストラクターを持っている」と「T x = std::move(anotherT);合法である」は同等ではないと言っているだけです。後者はmove-requestであり、Tにmove ctorがない場合、copy ctorにフォールバックする可能性があります。では、「可動」とは正確にはどういう意味ですか?
2013年

1
@Mehrdad:「MoveConstructible」の意味については、C ++標準ライブラリセクションをご覧ください。一部のイテレーターには移動コンストラクターがない場合がありますが、それでもMoveConstructibleです。人々が頭に浮かぶ「可動」のさまざまな定義に注意してください。
2013年

回答:


110

ハーブの回答(編集前)は、実際には移動可能ではないタイプの良い例を示してます:std::mutex

OSのネイティブミューテックスタイプ(pthread_mutex_tPOSIXプラットフォームなど)は、「ロケーションインバリアント」ではない可能性があります。これは、オブジェクトのアドレスがその値の一部であることを意味します。たとえば、OSは初期化されたすべてのミューテックスオブジェクトへのポインタのリストを保持する場合があります。場合はstd::mutex含まれて(OSはそのミューテックスへのポインタのリストを維持しているため)データメンバとネイティブ型のアドレスなどのネイティブOSのmutex型は、どちらか固定滞在する必要がありstd::mutex、それはで滞在するので、ヒープ上のネイティブmutex型を格納する必要がありますstd::mutexオブジェクト間で移動したときと同じ場所、またはstd::mutex移動してはいけません。にstd::mutexconstexprコンストラクタがあり、定数初期化(つまり静的初期化)に適格でなければならないため、ヒープに格納することはできません。std::mutexプログラムの実行が開始する前に構築されることが保証されているため、そのコンストラクタはを使用できませんnew。したがって、残された唯一の選択肢はstd::mutex不動であることです。

同じ理由が、固定アドレスを必要とする何かを含む他のタイプにも当てはまります。リソースのアドレスを固定したままにする必要がある場合は、移動しないでください。

移動std::mutexしていないときにmutexをロックしようとしている人がいないことを知っている必要があるため、安全に移動するのは非常に難しいという別の議論があります。mutexはデータの競合を防ぐために使用できる構成要素の1つであるため、競合自体に対して安全でなかった場合は残念です。不動のstd::mutex場合、いったん構築されて破棄される前に誰でもそれに対して実行できる唯一のことは、ロックとロック解除であり、それらの操作はスレッドセーフであることが明示的に保証されており、データの競合を招くことはありません。これと同じ引数がstd::atomic<T>オブジェクトに適用されます。オブジェクトをアトミックに移動できない場合、オブジェクトを安全に移動することができない場合、別のスレッドが呼び出しを試みている可能性がありますcompare_exchange_strong動かされた瞬間のオブジェクト。したがって、型を移動できない別のケースは、型が安全な並行コードの低レベルのビルディングブロックであり、型に対するすべての操作の原子性を保証する必要がある場合です。オブジェクトの値がいつでも新しいオブジェクトに移動される可能性がある場合は、アトミック変数を使用してすべてのアトミック変数を保護する必要があるため、それを使用しても安全であるか、移動されたかがわかります...そのアトミック変数など...

オブジェクトが純粋なメモリの一部であり、値または値の抽象化のホルダーとして機能するタイプではない場合、それを移動することは意味がないと一般的に言っていると思います。int移動できないなどの基本的なタイプ:移動は単なるコピーです。根性をから引き出すことはできません。intその値をコピーしてゼロに設定することはできますが、それでもint値を持つ、それは単なるメモリのバイトです。しかし、intまだ移動可能ですコピーは有効な移動操作だからです。ただし、コピー不可能なタイプの場合、メモリの一部を移動したくない、または移動できず、その値もコピーできない場合は、移動できません。ミューテックスまたはアトミック変数は、メモリの特定の場所(特別なプロパティで処理される)であるため、移動しても意味がなく、コピーもできないため、移動できません。


17
+1それは特別なアドレスを持っているので移動できない何かのエキゾチックではない例は有向グラフ構造のノードです。
Potatoswatter

3
mutexがコピー不可および移動不可の場合、mutexを含むオブジェクトをコピーまたは移動するにはどうすればよいですか?(同期用の独自のミューテックスを持つスレッドセーフクラスのように...)
tr3w

4
@ tr3w、ヒープ上にミューテックスを作成し、unique_ptrまたは同様のものを介してそれを保持しない限り、できません
Jonathan Wakely

2
@ tr3w:mutexの部分を除いて、クラス全体を移動しないのですか?
user541686

3
@BenVoigt、ただし新しいオブジェクトには独自のミューテックスがあります。彼は、mutexメンバーを除くすべてのメンバーを移動するユーザー定義の移動操作を持つことを意味していると思います。では、古いオブジェクトが期限切れになるとどうなりますか?そのミューテックスはそれとともに期限切れになります。
Jonathan Wakely 2013年

57

短い答え:タイプがコピー可能であれば、それも移動可能でなければなりません。ただし、その逆は当てはまりません。などの一部のタイプstd::unique_ptrは移動可能ですが、それらをコピーしても意味がありません。これらは当然移動のみのタイプです。

少し長い答えが続きます...

主に2種類のタイプがあります(他に、トレイトなどの特別な目的のタイプがあります)。

  1. intまたはなどの値のようなタイプvector<widget>。これらは値を表し、当然コピー可能でなければなりません。C ++ 11では、一般的に移動はコピーの最適化と考える必要があるため、すべてのコピー可能な型は当然移動可能である必要があります...元のオブジェクトはもう必要なく、とにかくそれを破壊するだけです。

  2. 基本クラスや仮想または保護されたメンバー関数を持つクラスなど、継承階層に存在する参照のような型。これらは通常、ポインタまたは参照によって保持され、多くの場合はbase*またbase&はであるため、スライスを回避するためのコピー構成は提供しません。既存のものと同じように別のオブジェクトを取得したい場合は、通常、のような仮想関数を呼び出しますclone。これらは、2つの理由で移動の構築または割り当てを必要としません。それらはコピー可能ではなく、すでにより効率的な自然な「移動」操作を備えています。オブジェクトにポインターをコピー/移動するだけで、オブジェクト自体は移動しません。まったく新しいメモリ位置に移動する必要があります。

ほとんどのタイプはこれら2つのカテゴリのいずれかに分類されますが、他にも有用なタイプがあり、まれにしかありません。特にここでは、などのリソースの一意の所有権を表すstd::unique_ptr型は、値のようではないため(コピーするのは無意味です)、当然ながら移動のみの型ですが、直接使用します(常にではありません)ポインタまたは参照で)、このタイプのオブジェクトをある場所から別の場所に移動したいとします。


61
本当のハーブサッターは、立ち上がってください?:)
fredoverflow 2013年

6
ええ、私は1つのOAuth Googleアカウントの使用から別のアカウントに切り替えましたが、ここで私に与えられる2つのログインをマージする方法を探すのに煩わされることはありません。(はるかに説得力のあるものの中で、OAuthに対するもう1つの議論です。)おそらくもう1つはもう使用しないので、これを時々SOポストで使用する予定です。
ハーブサッター2013年

7
std::mutexPOSIXミューテックスはアドレスで使用されるので、それは不動だと思いました。
パピー

9
@SChepurin:実際、それはHerbOverflowと呼ばれています。
sbi

26
これは多くの賛成票を得ていますが、タイプが移動のみである必要があると言っていることに気付いた人はいませんが、これは問題ではありませんか?:)
Jonathan Wakely 2013年

18

実際に調べてみると、C ++ 11のいくつかの型は移動できないことがわかりました。

  • すべてmutexのタイプ(recursive_mutextimed_mutexrecursive_timed_mutex
  • condition_variable
  • type_info
  • error_category
  • locale::facet
  • random_device
  • seed_seq
  • ios_base
  • basic_istream<charT,traits>::sentry
  • basic_ostream<charT,traits>::sentry
  • すべてのatomicタイプ
  • once_flag

どうやらClangに関する議論がありますhttps : //groups.google.com/forum/? fromgroups=#!topic/comp.std.c++/ pCO1Qqb3Xa4


1
...イテレータは移動できませんか?何...なぜ?
user541686 2013年

ええ、iterators / iterator adaptorsC ++ 11にはmove_iteratorがあるので、編集する必要があると思いますか?
ビルズ2013年

さて、私は混乱しています。ターゲットを移動するイテレータについて、またはイテレータ自体を移動することについて話していますか?
user541686 2013年

1
そうですstd::reference_wrapper。さて、他の人は実際には移動できないようです。
Christian Rau、2013年

1
これらは、3つのカテゴリに分類されているように見える。1.低レベルの同時実行性に関連したタイプ(アトミック、ミューテックス)、2.多型基底クラス(ios_basetype_infofacet)、3盛り合わせ奇妙なもの(sentry)。おそらく、平均的なプログラマーが作成する唯一の移動不可能なクラスは、2番目のカテゴリーにあります。
Philipp

0

私が見つけたもう1つの理由-パフォーマンス。値を保持するクラス「a」があるとします。ユーザーが限られた時間(スコープ)の値を変更できるインターフェースを出力したいとします。

これを達成する方法は、次のように、デストラクタに値を設定する「a」から「スコープガード」オブジェクトを返すことです。

class a 
{ 
    int value = 0;

  public:

    struct change_value_guard 
    { 
        friend a;
      private:
        change_value_guard(a& owner, int value) 
            : owner{ owner } 
        { 
            owner.value = value;
        }
        change_value_guard(change_value_guard&&) = delete;
        change_value_guard(const change_value_guard&) = delete;
      public:
        ~change_value_guard()
        {
            owner.value = 0;
        }
      private:
        a& owner;
    };

    change_value_guard changeValue(int newValue)
    { 
        return{ *this, newValue };
    }
};

int main()
{
    a a;
    {
        auto guard = a.changeValue(2);
    }
}

change_value_guardを移動可能にした場合、ガードが移動したかどうかをチェックするデストラクタに「if」を追加する必要があります。これは、追加のifであり、パフォーマンスに影響します。

ええ、確かに、それはおそらく任意の正気なオプティマイザによって最適化することができますが、それでも言語(これはC ++ 17が必要ですが、移動不可能な型を返すことができるために保証されたコピー省略が必要です)は私たちを必要としませんガードを作成関数から戻す以外に移動しない場合は、それを支払う必要があります(dont-pay-for-what-you-dont-useの原則)。

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