1. ホーム
  2. c++

[解決済み] C++11でunordered_mapが挿入したものを破壊するのはC++標準化委員会の意図するところですか?

2022-09-25 01:58:11

質問

私は、unordered_map::insert()が挿入した変数を破壊するという非常に奇妙なバグを追跡するために、人生の3日間を失ったところです。この非常に明白でない動作は、非常に最近のコンパイラでのみ発生します:私は clang 3.2-3.4 と GCC 4.8 を見つけました。 だけです。 コンパイラであることがわかりました。

以下は、私のメイン コード ベースから、この問題を実証するいくつかの縮小されたコードです。

#include <memory>
#include <unordered_map>
#include <iostream>

int main(void)
{
  std::unordered_map<int, std::shared_ptr<int>> map;
  auto a(std::make_pair(5, std::make_shared<int>(5)));
  std::cout << "a.second is " << a.second.get() << std::endl;
  map.insert(a); // Note we are NOT doing insert(std::move(a))
  std::cout << "a.second is now " << a.second.get() << std::endl;
  return 0;
}

私は、おそらくほとんどのC++プログラマと同様に、出力がこのように見えることを期待しています。

a.second is 0x8c14048
a.second is now 0x8c14048

しかし、clang 3.2-3.4 と GCC 4.8 では、代わりに次のようなものが得られます。

a.second is 0xe03088
a.second is now 0

のunordered_map::insert()のドキュメントをよく見てみると、これは意味がわからないかもしれません。 http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ にあるunordered_map::insert()のドキュメントをよく調べるまで、意味をなさないかもしれません。

template <class P> pair<iterator,bool> insert ( P&& val );

これは貪欲な普遍的参照移動オーバーロードで、他のオーバーロードのどれにもマッチしないものを消費し、そして を構築します。 を構築して、value_type にします。では、なぜ上記のコードはこのオーバーロードを選択し、おそらくほとんどの人が期待するような unordered_map::value_type オーバーロードを選択しなかったのでしょうか?

答えはあなたの顔をじっと見ています:unordered_map::value_typeはpair< const int, std::shared_ptr> であり、コンパイラは正しくpair<.と考えるでしょう。 int std::shared_ptr> が変換可能でないとコンパイラは正しく判断します。したがって、コンパイラは移動普遍参照のオーバーロードを選択し、それはオリジナルを破壊します。 にもかかわらず プログラマがstd::move()を使っていないにもかかわらず。したがって、挿入による破壊の動作は実際には 正しい であり、古いコンパイラは 不正解 .

なぜ私がこのバグを診断するのに3日間もかかったのか、今ならおそらくおわかりでしょう。unordered_map に挿入される型がソース コード用語で遠く離れた場所で定義された typedef であり、typedef が value_type と同一であるかどうかを確認することは誰にも思いつかなかった、大規模なコード ベースではまったく明らかではありませんでした。

そこで、Stack Overflow への私の質問です。

  1. なぜ古いコンパイラは新しいコンパイラのように挿入された変数を破棄しないのでしょうか?つまり、GCC 4.7 でさえこれを行わず、かなり標準に準拠しているのです。

  2. コンパイラをアップグレードすると、確かに以前は動作していたコードが突然動作しなくなるので、この問題は広く知られているのでしょうか?

  3. C++ 標準委員会はこの動作を意図していたのでしょうか?

  4. より良い動作を与えるために unordered_map::insert() をどのように修正することを提案しますか?もしここでサポートがあれば、私はこの動作を WG21 に N ノートとして提出し、より良い動作を実装するよう依頼するつもりなので、これを尋ねます。

どのように解決するのですか?

他の人がコメントで指摘しているように、quot;universal" コンストラクタは実際には、常にその引数から移動することになっているわけではありません。引数が本当に rvalue である場合は移動し、lvalue である場合はコピーすることになっています。

あなたが観察した、常に移動する動作は、libstdc++ のバグであり、質問に対するコメントによると、現在は修正されています。好奇心が強い人のために、私は g++-4.8 のヘッダーを調べました。

bits/stl_map.h , 598-603行目

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_t._M_insert_unique(std::forward<_Pair>(__x)); }

bits/unordered_map.h 365行目から370行目

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_h.insert(std::move(__x)); }

後者は、誤って std::move を使うべきところ std::forward .