C++中嵌套unordered_map操作为何需移动构造函数?自定义类赋值报错解析
嘿,我来帮你把这个问题掰扯清楚~
首先你得明白:不是嵌套unordered_map“非要”用移动构造函数,而是它在尝试默认构造你的自定义对象时找不到合适的构造函数,才把移动构造当成了候选之一,最后匹配失败报错。
为什么会触发这个错误?
当你用outer_map[key1][key2] = ...这种方式赋值时,unordered_map的operator[]会做两件事:
- 如果
key1不在外层map里,外层map会自动默认构造一个内层的unordered_map插进去; - 如果
key2不在内层map里,内层map会尝试默认构造一个你的MyObj对象插进去,再返回它的引用让你赋值。
但你的MyObj类既没有显式写默认构造函数,又因为某些原因(比如类里有const成员、引用成员,或者基类不支持默认/移动构造),编译器也没法自动生成隐式的默认构造函数。这时候编译器会把所有能找到的构造函数都拿出来试,包括本该自动生成但实际上被禁用的隐式移动构造——可移动构造需要一个右值参数,你这里是要0参数构造,所以就出现了那个报错:“候选构造函数(隐式移动构造函数)不可行:需要1个参数,但提供了0个”。
简单说:核心需求是默认构造MyObj,但找不到合适的构造函数,移动构造只是被拉来凑数的候选而已。
怎么解决这个问题?
给你几个实用的方案,根据你的场景选:
1. 给MyObj加个显式默认构造函数
这是最直接的办法,如果你的类逻辑允许(比如成员变量有合理的默认值),直接加就行:
class MyObj { public: MyObj() = default; // 用编译器生成的默认构造,或者自己写逻辑实现 // 你的其他构造函数、成员函数... };
这样unordered_map的operator[]就能正常创建对象,不会再去碰移动构造了。
2. 别用operator[],改用insert/emplace
如果不想给MyObj加默认构造,那就手动构造好对象再插进去,用insert或emplace代替operator[]:
// 先给外层map插一个空的内层map outer_map.emplace(key1, std::unordered_map<Key, MyObj>{}); // 再给内层map插已构造好的MyObj outer_map.at(key1).emplace(key2, MyObj{/*这里传构造参数*/});
这里用at()而不是operator[],因为at()只会找已存在的键,找不到就抛异常,不会偷偷默认构造对象。
或者更简洁的写法,一次性插入外层和内层的内容:
outer_map.emplace(key1, std::initializer_list<std::pair<const Key, MyObj>>{ {key2, MyObj{/*构造参数*/}} });
3. 顺便说下为啥隐式移动构造没法生成
虽然你没打算用移动构造,但搞清楚原因也有帮助:C++11里编译器会自动生成隐式移动构造,但如果你的类满足以下任一情况,这个自动生成会被禁用:
- 你显式写了拷贝构造、拷贝赋值运算符;
- 你显式写了析构函数;
- 类里有const成员、引用成员,或者成员本身是个没有移动构造的类;
- 基类没有移动构造,或者基类的移动构造是私有/受保护的。
你的MyObj应该属于上述某一种情况,所以编译器没法生成隐式移动构造,这也是报错里说它“不可行”的原因。
内容的提问来源于stack exchange,提问作者David

