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容器中,在此过程中创建两个额外的临时副本:

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我们可以通过将签名更改为 来摆脱一个额外的复制构造函数调用std::pair<int, element&>,但是,第二个临时对象仍然被创建并立即被销毁:

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'smove功能的众多用例之一,由许多新功能支持,尤其是右值引用,以及各种新的标准库接口,包括std::map::emplacestd::vector::emplace_back等。

如果由于某种原因您还不能使用C++11,您至少可以安慰自己,认为问题已被识别,解决方案已标准化和实施,而且我们中的许多人正在使用它,我们中的一些人[1] 在生产代码中。所以,就像老笑话所说的那样,存在一个解决方案,你什么时候接受它是你的决定。

请注意,如果您的对象实现移动构造函数,则不必使用emplace成员函数,它们甚至可以默认使用。如果具有显式复制构造函数,则不会发生这种情况,因此您上面的测试可能会产生观察者效果(实际上,在 POD 的情况下,它也可能会抑制编译器优化,因此即使使用 C++03,您也可能不会遇到问题认为你这样做)。

有多种可用的技巧可以避免只对“次要”源代码更改进行复制,但恕我直言,最好的方法是开始转向 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 回答