unordered_map :: insert()が挿入した変数を破壊するという非常に奇妙なバグを追跡して、3日間の人生を失ったところです。この非常に明白でない動作は、ごく最近のコンパイラーでのみ発生します。clang3.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
これは、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はペア< const int、std :: shared_ptr>であり、コンパイラーはペア< int、std :: shared_ptr>は変換可能ではないと正しく認識します。したがって、プログラマーがstd :: move()を使用していないにもかかわらず、コンパイラーはユニバーサルムーブリファレンスのオーバーロードを選択し、元のオブジェクトを破棄します。そのための行動を破壊し、インサートは、実際には正しい C ++ 11標準あたりとして、そして古いコンパイラであった正しくありません。
私がこのバグを診断するのに3日間かかった理由が今わかるでしょう。unordered_mapに挿入される型がソースコード用語で遠く離れて定義されたtypedefである大規模なコードベースではまったく明らかではなく、typedefがvalue_typeと同一であるかどうかを確認することは誰にも起こりませんでした。
スタックオーバーフローへの私の質問:
古いコンパイラーが新しいコンパイラーのように挿入された変数を破棄しないのはなぜですか?つまり、GCC 4.7でもこれは行われず、かなり標準に準拠しています。
コンパイラを確実にアップグレードすると、以前は機能していたコードが突然機能しなくなるため、この問題は広く知られていますか?
C ++標準委員会はこの動作を意図しましたか?
unordered_map :: insert()を変更して動作を改善することをどのように提案しますか?ここでサポートがある場合は、この動作をWG21へのNノートとして送信し、より良い動作を実装するように依頼するつもりなので、これを質問します。
4.9.0 20131223 (experimental)
それぞれgcc 4.8.2を使用しています。出力はa.second is now 0x2074088
(または同様の)です。
a
です。コピーを作成する必要があります。また、この動作は、コンパイラではなくstdlibに完全に依存しています。