C++中插入对象到unordered_map与vector时能否避免拷贝构造调用?
问题根源
你遇到的两次拷贝构造分别来自两个操作:
map[x1]访问时,左值x1会被拷贝一份作为unordered_map的key存入节点emplace_back(x2)传入左值x2,会拷贝一份构造vector的新元素
最低改动方案:用移动替代拷贝
对于支持移动构造的类型(你给出的CustomClass已经实现了移动构造),仅需修改一行代码,即可用开销极低的移动构造完全替代两次拷贝构造:
// 将x1移动到map作为key,将x2移动到vector作为元素 map[std::move(x1)].emplace_back(std::move(x2));
修改后原来的两次拷贝构造会变成两次移动构造,对于仅包含基础成员的类型,移动构造的开销几乎可以忽略。
完全原地构造方案(零拷贝零移动,对象仅构造一次)
如果要完全避免拷贝、移动,实现对象仅在最终存储位置构造一次,可以配合C++17引入的try_emplace方法实现:
方案前提(二选一即可)
- 给目标类型添加接收
std::istream&的构造函数,直接从输入流初始化对象:
CustomClass(std::istream& in) { std::cout << "istream constructor" << std::endl; in >> m_data; }
- 若不能修改目标类型定义,可先将输入内容读取为基础类型的临时变量,再传入构造。
代码实现
适配istream构造的版本:
std::cout << "----" << std::endl; // try_emplace直接在map节点原地构造key,返回迭代器 auto [it, _] = map.try_emplace(std::cin); // emplace_back直接在vector的内存空间原地构造元素 it->second.emplace_back(std::cin); std::cout << "----" << std::endl; std::cout << it->second[0] << std::endl; std::cout << "----" << std::endl;
整个过程仅触发两次自定义构造函数,无任何拷贝、移动操作。
不修改自定义类的通用版本:
std::cout << "----" << std::endl; int key_val, elem_val; // 先读取基础类型值,无自定义类开销 std::cin >> key_val >> elem_val; // 直接用读取到的值原地构造map的key和vector的元素 auto [it, _] = map.try_emplace(key_val); it->second.emplace_back(elem_val); std::cout << "----" << std::endl; std::cout << it->second[0] << std::endl; std::cout << "----" << std::endl;
该方案对所有可从对应基础类型构造的类、基础类型都适用,全程无自定义类的拷贝、移动操作。
方案说明
unordered_map::try_emplace是C++17新增的接口,会在键不存在时直接将传入的参数转发给键的构造函数,在map分配的节点内存上原地构造,完全避免了临时对象的拷贝或移动。vector::emplace_back会直接将传入的参数转发给元素的构造函数,在vector预分配的内存上原地构造元素,避免了临时对象的拷贝或移动。
内容的提问来源于stack exchange,提问作者TheMemeMachine
相关产品推荐
相关产品推荐

