これが検索結果に表示されなかったことに驚きました。C++ 11での移動セマンティクスの有用性を考えると、誰かがこれを以前に尋ねたことがあると思いました。
(理由other既存のコードとの互換性の問題よりも、つまり。)
(編集される前の)ハーブの答えは、実際にタイプの良い例を示しました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です。ただし、コピー不可の型の場合、メモリの一部を移動したくない、または移動できず、その値もコピーできない場合は、移動できません。ミューテックスまたはアトミック変数はメモリの特定の場所(特別なプロパティで処理)であるため、移動しても意味がなく、コピーもできないため、移動できません。
簡単な答え:型がコピー可能な場合、それも移動可能でなければなりません。ただし、その逆は当てはまりません。std::unique_ptr
などの一部の型は移動可能ですが、コピーする意味はありません。これらは当然、移動のみのタイプです。
少し長い回答が続きます...
2つの主要なタイプのタイプがあります(特性など、他のより特殊なタイプの中でも)
int
やvector<widget>
などの値のような型。これらは値を表し、当然コピー可能である必要があります。 C++ 11では、一般に移動はコピーの最適化と考える必要があります。したがって、すべてのコピー可能なタイプは自然に移動可能である必要があります。移動は、よくあるケースでコピーを行う効率的な方法です。 tは元のオブジェクトを必要としなくなり、とにかく破棄するだけです。
基本クラスや仮想または保護されたメンバー関数を持つクラスなど、継承階層に存在する参照のような型。これらは通常、ポインタまたは参照(多くの場合base*
またはbase&
)によって保持されるため、スライスを回避するためのコピー構成を提供しません。既存のオブジェクトのように別のオブジェクトを取得したい場合は、通常clone
のような仮想関数を呼び出します。これらは2つの理由で移動の構築または割り当てを必要としません:それらはコピー可能ではなく、さらに効率的な自然な「移動」操作が既にあります-オブジェクトへのポインタをコピー/移動するだけで、オブジェクト自体はありません新しいメモリの場所に移動する必要があります。
ほとんどのタイプはこれら2つのカテゴリのいずれかに分類されますが、他にも有用なタイプのタイプがありますが、これはまれです。特にここでは、std::unique_ptr
などのリソースの一意の所有権を表す型は、値に似ていない(コピーする意味がない)ため、当然移動のみの型ですが、直接(常にポインタや参照によってではありません)、そのため、このタイプのオブジェクトをある場所から別の場所に移動したい場合。
実際に検索すると、C++ 11のかなりの型が移動できないことがわかりました。
mutex
タイプ(recursive_mutex
、timed_mutex
、recursive_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
私が見つけたもう一つの理由-パフォーマンス。値を保持するクラス「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原則)。