6

このプログラムを検討してください。

#include <map>
#include <string>
#define log magic_log_function // Please don't mind this.

//
// ADVENTURES OF PROGO THE C++ PROGRAM
//

class element;
typedef std::map<int, element> map_t;

class element {
public:
    element(const std::string&);
    element(const element&);
    ~element();
    std::string name;
};
element::element(const std::string& arg)
    : name(arg)
{
    log("element ", arg, " constucted, ", this);
}
element::element(const element& other)
    : name(other.name)
{
    name += "-copy";
    log("element ", name, " copied, ", this);
}
element::~element()
{
    log("element ", name, " destructed, ", this);
}
int main(int argc, char **argv)
{
    map_t map1; element b1("b1");
    log(" > Done construction.");
    log(" > Making map 1.");
    map1.insert(std::pair<int, element>(1, b1));
    log(" > Done making map 1.");
    log(" > Before returning from main()");
}

スタック上にいくつかのオブジェクトを作成insertし、それらをstd::mapコンテナに入れて、プロセスで2つの追加の一時コピーを作成します。

element b1 constucted, 0x7fff228c6c60
 > Done construction.
 > Making map 1.
element b1-copy copied, 0x7fff228c6ca8
element b1-copy-copy copied, 0x7fff228c6c98
element b1-copy-copy-copy copied, 0x232d0c8
element b1-copy-copy destructed, 0x7fff228c6c98
element b1-copy destructed, 0x7fff228c6ca8
 > Done making map 1.
 > Before returning from main()
element b1 destructed, 0x7fff228c6c60
element b1-copy-copy-copy destructed, 0x232d0c8

std::pair署名をに変更することで、余分なコピーコンストラクター呼び出しを1つ取り除くことができますがstd::pair<int, element&>、2番目の一時的なものは引き続き作成され、すぐに破棄されます。

element b1 constucted, 0x7fff0fe75390
 > Done construction.
 > Making map 1.
element b1-copy copied, 0x7fff0fe753c8
element b1-copy-copy copied, 0x1bc4098
element b1-copy destructed, 0x7fff0fe753c8
 > Done making map 1.
 > Before returning from main()
element b1 destructed, 0x7fff0fe75390
element b1-copy-copy destructed, 0x1bc4098

参照によってスタック上のオブジェクトを取得し、その単一の内部コピーを作成する方法はありstd::mapますか?

4

4 に答える 4

9

これは、C++11の機能を動機付けた多くのユースケースの1つであり、多くの新機能、特に右辺値参照、および、などmoveを含むさまざまな新しい標準ライブラリインターフェイスによってサポートされています。std::map::emplacestd::vector::emplace_back

何らかの理由でまだ使用できないC++11場合は、少なくとも、問題が認識され、ソリューションが標準化および実装されており、さらに多くの人がそれを使用している、一部の人がそれを使用しているという考えで自分を慰めることができます[1]プロダクションコード。だから、古いジョークがそれを持っているように、解決策が存在し、それをいつ取り上げるかについてのあなたの呼びかけです。

emplaceオブジェクトがmoveコンストラクターを実装している場合は、メンバー関数を使用する必要がないことに注意してください。これは、デフォルトで実行される場合もあります。明示的なコピーコンストラクターがある場合、これは発生しないため、上記のテストでオブザーバー効果が生成される可能性があります(実際、PODの場合はコンパイラーの最適化も抑制される可能性があるため、C++03でも問題が発生しない可能性があります。あなたがそう思う)。

「マイナーな」ソースコードの変更のみでコピーを回避するさまざまなハックが利用可能ですが、IMHOの最善のアプローチは、C++11への移行を開始することです。何をするにしても、避けられない移行の苦痛を軽減する方法でそれを行うようにしてください。


[注1]:免責事項:私は、多かれ少なかれ引退したプロダクションコードを作成しなくなったため、その文の「私たちの一部」の一部ではありません。

于 2012-12-14T17:29:52.757 に答える
5

私が行ってきた標準的な方法(古いC ++バージョンの場合)は、共有ポインターのマップを使用することです。

それでも共有ポインタのコピーを作成しますが、それは通常、大きなオブジェクトをコピーするよりもはるかに面倒ではありません。

于 2012-12-14T20:22:23.223 に答える
3

emplace()を使用できます:

要素はインプレースで構築されます。つまり、コピーまたは移動操作は実行されません。要素型のコンストラクター(value_type、つまりstd :: pair)は、関数に提供されたものとまったく同じ引数で呼び出されます

于 2012-12-14T14:33:06.760 に答える
0

がない場合はemplace、ヒープ上に要素を作成し、ポインタをマップに渡すことができます。

typedef std::map<int, element*> map_t;
...
printf(" > Making pair 1.\n");
std::pair<int, element*> pair(1, new element ("b1")) ;
printf(" > Making map 1.\n");
map1.insert(pair);

ただし、マップがスコープを離れるときに注意しないと、メモリリークが発生する可能性があります...

于 2012-12-14T14:59:39.587 に答える