为何使用piecewise_construct参数的unordered_map::emplace要求默认构造函数?
为什么用
piecewise_construct插入unordered_map还需要默认构造函数? 这个问题其实是旧版本Visual Studio(VC14/VS2015)标准库的实现bug导致的——按C++标准的设计,std::piecewise_construct的核心作用就是帮你在容器里直接构造没有默认构造函数的value类型,完全不需要默认构造。
先理清楚正常的预期行为
当你调用map.emplace(std::piecewise_construct, ...)时,底层逻辑应该是这样的:
- 容器先为新的键值对(
std::pair<const std::string, A>)分配内存 - 然后分别用你提供的两个tuple参数,直接在分配好的内存里构造key和value:
- 第一个tuple(
forward_as_tuple(name))用来构造key(std::string) - 第二个tuple(
forward_as_tuple(constructorArg1, ...))用来构造value(A)
整个过程完全不会触发A的默认构造函数——这也是piecewise_construct专门为这类场景设计的原因。
- 第一个tuple(
为什么你的VS2015会报错?
VC14(也就是VS2015)的标准库在实现std::pair的分段构造逻辑时,存在一个缺陷:它的tuple头文件(就是你报错的tuple 1180行)里的代码错误地要求A必须有默认构造函数,哪怕我们明确用piecewise_construct来跳过默认构造。这个问题是MSVC旧版本的已知问题,在VS2017及以后的版本中已经被修复了。
解决办法
如果你必须留在VS2015环境下,可以试试这些方案:
- 临时添加私有默认构造函数:给
A加一个私有的默认构造函数,然后把std::pair或者unordered_map声明为友元。这样编译器不会报错,但实际运行时不会调用这个默认构造函数(因为分段构造会直接用你的参数构造A)。 - 换用
insert+移动构造:先构造好完整的std::pair<const std::string, A>,再用map.insert(std::move(pair))插入。如果A支持移动构造,这个开销很小。 - 升级VS版本:最彻底的解决方式是升级到VS2017或更高版本,新版本的标准库已经修复了这个bug,
piecewise_construct可以正常工作。
举个简单的验证代码(VS2015报错,VS2017+正常):
#include <unordered_map> #include <string> #include <tuple> class A { public: A(int a, int b, int c) {} // 无默认构造函数 }; int main() { std::unordered_map<std::string, A> my_map; std::string name = "demo"; my_map.emplace(std::piecewise_construct, std::forward_as_tuple(name), std::forward_as_tuple(1, 2, 3)); return 0; }
内容的提问来源于stack exchange,提问作者Tudvari
相关产品推荐
相关产品推荐

