将元素插入std :: map而无需额外复制

Mis*_*iev 6 c++ optimization stdmap c++-standard-library

考虑这个程序:

#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()");
}
Run Code Online (Sandbox Code Playgroud)

它在堆栈上创建一些对象并将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
Run Code Online (Sandbox Code Playgroud)

我们可以通过更改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
Run Code Online (Sandbox Code Playgroud)

有没有办法std::map通过引用将对象放在堆栈上并制作一个内部副本?

ric*_*ici 8

这是众多使用案例其中一个动机C++11move功能,通过新的功能,特别是右值引用主机支持,以及各种新的标准库接口,包括std::map::emplace,std::vector::emplace_back等等.

无论出于何种原因,如果你还没有使用C++11,你至少可以安慰自己认为问题已被认可,并且解决方案已经标准化并实施,而且我们当中许多人都在使用它,我们中的一些人[1]在生产代码中.所以,正如老笑话所说的那样,存在一种解决方案,这就是你何时接受它的呼吁.

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

有各种各样的黑客可用,有点 - 避免副本只有"次要"的源代码更改,但恕我直言,最好的方法是开始转向C++ 11.无论你做什么,都要尝试以不可避免的迁移减少痛苦的方式来做.


[注1]:免责声明:我不再编写生产代码,或多或少退休,所以我不是那句话中"我们中的一些人"的一部分.


pat*_*ros 5

我所使用的标准做法(使用较旧的 C++ 版本)是使用共享指针的 Map。

仍然创建共享指针的副本,但这通常比复制大对象要简单得多。