`const`オブジェクトで` std :: move`を使用できるのはなぜですか?


112

C ++ 11では、次のコードを記述できます。

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

を呼び出すとstd::move、オブジェクトを移動することを意味します。つまり、オブジェクトを変更します。移動するにはconstオブジェクトは不合理ですが、なぜstd::moveこの動作を制限しないのですか?将来的には罠になりますよね?

ここでトラップとは、ブランドンがコメントで言及したとおりです。

「彼はそれが彼をこっそりとこっそりと「罠にかける」ことを意味していると思う。もし彼が気付かないと、彼は結局彼が意図したものではないコピーで終わるからだ。」

スコットマイヤーズの著書「Effective Modern C ++」で、彼は例を挙げています。

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

オブジェクトのstd::move操作が禁止されていればconst、バグを簡単に見つけることができますよね?


2
しかし、それを動かしてみてください。状態を変更してみてください。std::moveそれ自体はオブジェクトに対して何もしません。std::move名前の付け方が不十分だと主張する人もいます。
juanchopanza 2015

3
実際には何も動かしません。それが行うすべては右辺値参照にキャストされます。通常の移動割り当てCAT cat2 = std::move(cat);CATサポートしていると仮定して、を試してください。
WhozCraig、2015

11
std::move単なるキャストであり、実際には何も動かしません
レッドアラート

2
@WhozCraig:注意してください。投稿したコードは警告なしでコンパイルおよび実行されるため、誤解を招く恐れがあります。
Mooing Duck 2015

1
@MooingDuckコンパイルしないと言ったことはありません。デフォルトのcopy-ctorが有効になっているためにのみ機能します。それをスケルチすると、ホイールが外れます。
WhozCraig、2015

回答:


55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

ここではstd::moveonの使用を確認しますT const。を返しますT const&&strangeまさにこのタイプを取るためのmoveコンストラクターがあります。

そして呼ばれる。

さて、この奇妙なタイプはあなたの提案が修正するバグよりもまれであることは事実です。

しかし、その一方で、既存のstd::moveコードは一般的なコードでより適切に機能します。この場合、操作している型がaであるTかa であるかがわかりませんT const


3
実際にあなたがする理由を説明しようとする最初の答えであることのための+1 たい呼び出すためstd::moveconstオブジェクト。
Chris Drew

+1は、関数の実行を示しますconst T&&。これは、「rvalue-refを使用しますが、変更しないことを約束します」という種類の「APIプロトコル」を表します。ミュータブルを使用する場合を除いて、それは珍しいと思います。多分別のユースケースはforward_as_tuple、ほとんど何でも使用でき、後でそれを使用できるようにすることです。
Fペレイラ

107

見落としているトリックがあります。つまり、std::move(cat) 実際には何も動かしません。コンパイラに移動を試みるように指示するだけです。ただし、クラスにはを受け入れるコンストラクターがないconst CAT&&ため、代わりに暗黙的なconst CAT&コピーコンストラクターが使用され、安全にコピーされます。危険なし、罠なし。何らかの理由でコピーコンストラクターが無効になっていると、コンパイラエラーが発生します。

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

プリントCOPYではなくMOVE

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

言及したコードのバグはパフォーマンスの問題であり、安定性の問題ではないことに注意してください。そのようなバグが原因でクラッシュが発生することはありません。遅いコピーを使用するだけです。さらに、このようなバグはmoveコンストラクターを持たない非constオブジェクトでも発生するため、単にconstオーバーロードを追加するだけではすべてがキャッチされません。構成を移動する機能またはパラメーターの型から割り当てを移動する機能をチェックすることもできますが、コピーコンストラクターにフォールバックするはずの汎用テンプレートコードに干渉します。そして、一体、誰かがから構築できるようになりたいと思っているかもしれconst CAT&&ません。


気になった。通常のmove-constructorまたは代入演算子のユーザー定義の定義でのcopy-ctorの暗黙的な削除に注意する価値があることは、壊れたコンパイルによってこれを実証することにもなります。素敵な答え。
WhozCraig、2015

また、const左辺値以外を必要とするコピーコンストラクタも役に立たないことにも言及する価値があります。[class.copy]§8:「それ以外の場合、暗黙的に宣言されたコピーコンストラクターの形式は次のとおりですX::X(X&)
Deduplicator

9
私は彼がコンピュータ/アセンブリ用語で「罠」を意味したとは思いません。彼がそれは彼が卑劣な卑劣なことを「罠にかける」ことを意味すると私は思う。たぶん..
ブランドン

ゴールドバッジの場合、さらに1票、4票獲得してください。;)
Yakk-Adam Nevraumont、2015

そのコードサンプルのハイファイブ。非常によくポイントを取得します。
ブレントがコード

20

残りの回答でこれまで見過ごされてきた理由の1つは、一般的なコードが移動しても回復力を発揮できることです。たとえば、ある種類のコンテナーからすべての要素を移動して同じ値を持つ別の種類のコンテナーを作成する汎用関数を作成したいとします。

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

かっこいい、今では比較的効率的にa vector<string>を作成でき、その過程でdeque<string>各個人stringが移動します。

しかし、から移動したい場合はどうなりmapますか?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

std::moveconst引数を主張した場合、上記ののインスタンス化はmove_eachconst intkey_typemap)を移動しようとしているため、コンパイルされません。ただし、このコード、を移動できないかどうかには関係ありませんkey_type。移動したいmapped_typestd::stringパフォーマンス上の理由から)。

これはこの例のためのものであり、移動の要求std::moveはなく、移動の要求である一般的なコーディングにおける他の無数の例です。


2

OPと同じ懸念があります。

std :: moveはオブジェクトを移動しません。オブジェクトが移動可能であることも保証しません。では、なぜそれをmoveと呼ぶのでしょうか。

移動できないことは、次の2つのシナリオのいずれかになる可能性があると思います。

1.移動タイプはconstです。

言語にconstキーワードがあるのは、constであると定義されたオブジェクトへの変更をコンパイラーに禁止するためです。スコットマイヤーズの本の例を考えると:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

それは文字通りどういう意味ですか?const文字列を値のメンバーに移動します-少なくとも、説明を読む前に理解しておきます。

言語がstd :: move()の呼び出し時に移動を行わない、または移動が適用可能であることを保証しない場合、単語の移動を使用すると文字通り誤解を招く恐れがあります。

言語がstd :: moveを使用して効率を向上させることを奨励している場合、特にこの種の明白な文字の矛盾については、このようなトラップをできるだけ早く防止する必要があります。

定数を移動することは不可能であることを人々が認識している必要があることに私は同意しますが、この義務は、明らかな矛盾が発生した場合にコンパイラーが沈黙できることを意味するものではありません。

2.オブジェクトには移動コンストラクタがありません

個人的には、クリスドリューが言ったように、これはOPの懸念とは別の話だと思います

@hvdそれは私には少し議論のようではないようです。OPの提案が世界中のすべてのバグを修正しないからといって、必ずしもそれが悪い考えであることを意味するわけではありません(おそらくそうですが、あなたが与える理由ではありません)。–クリスドリュー


1

誰もがこれの後方互換性の側面に言及しなかったことに驚いています。私は、std::moveC ++ 11でこれを行うために意図的に設計されたと思います。C ++ 98ライブラリに大きく依存しているレガシーコードベースを使用しているとしましょう。コピーの割り当てにフォールバックしないと、移動すると問題が発生します。


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