web-dev-qa-db-ja.com

C ++ 11で型を移動不可にする場合

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

C++ 11でクラスを移動不可にする必要があるのはいつですか?

(理由other既存のコードとの互換性の問題よりも、つまり。)

120
Mehrdad

(編集される前の)ハーブの答えは、実際にタイプの良い例を示しましたshould n't移動可能:std::mutex

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

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

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

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

107
Jonathan Wakely

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

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

2つの主要なタイプのタイプがあります(特性など、他のより特殊なタイプの中でも)

  1. intvector<widget>などの値のような型。これらは値を表し、当然コピー可能である必要があります。 C++ 11では、一般に移動はコピーの最適化と考える必要があります。したがって、すべてのコピー可能なタイプは自然に移動可能である必要があります。移動は、よくあるケースでコピーを行う効率的な方法です。 tは元のオブジェクトを必要としなくなり、とにかく破棄するだけです。

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

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

57
Herb Sutter

実際に検索すると、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

17
billz

私が見つけたもう一つの理由-パフォーマンス。値を保持するクラス「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-dont-use原則)。

0
saarraz1