C++中unordered_map::insert的隐式转换与构造函数调用问题
C++ unordered_map insert 构造函数调用疑问解析
代码
#include <iostream> #include <unordered_map> #include <utility> using namespace std; struct Foo { Foo(const int value) : val(value) { cout << "Foo(int), val: " << val << '\n'; } Foo(Foo & foo) { val = foo.val; cout << "Foo(Foo &)" << '\n'; } Foo(const Foo & foo) { val = foo.val; cout << "Foo(const Foo &)" << '\n'; } Foo(Foo && foo) { val = foo.val; cout << "Foo(Foo &&)" << '\n'; } ~Foo() { cout << "~Foo(), val: " << val << '\n'; } Foo& operator=(const Foo& rhs) { cout << "Foo& operator=(const Foo& rhs), rhs.val: " << rhs.val; val = rhs.val; return *this; } bool operator==(const Foo& rhs) const { return val == rhs.val; } bool operator<(const Foo& rhs) const { return val < rhs.val; } int val; }; template<> struct std::hash<Foo> { size_t operator()(const Foo& f) const { return hash<int>{}(f.val); } }; int main() { std::unordered_map<Foo, int> mp; mp.insert(std::pair<Foo, int>{1, 50}); std::cout << '\n'; mp.insert(std::pair<const Foo, int>{2, 60}); std::cout << '\n'; std::cout << "exiting main()\n"; }
运行输出
Foo(int), val: 1 Foo(Foo &&) ~Foo(), val: 1 Foo(int), val: 2 Foo(const Foo &) ~Foo(), val: 2 exiting main() ~Foo(), val: 1 ~Foo(), val: 2
疑问与解答
疑问1:为什么mp.insert(std::pair<Foo, int>{1, 50})可以编译?是否发生了隐式转换?
可以编译,确实发生了隐式转换,具体过程如下:
std::pair<Foo, int>{1, 50}先调用Foo(int)构造临时Foo对象,输出Foo(int), val: 1,同时构造出临时的std::pair<Foo, int>对象。std::unordered_map<Foo, int>的value_type是std::pair<const Foo, int>,而std::pair提供了模板构造函数,允许从std::pair<U, V>隐式构造std::pair<T1, T2>,只要U可转换为T1、V可转换为T2。这里Foo可以直接转换为const Foo(仅添加const修饰,无需构造新对象),int与int完全匹配,因此临时的std::pair<Foo, int>可以隐式转换为std::pair<const Foo, int>右值,匹配insert的value_type&&重载。- 由于传入的是右值,map内部分配节点时会通过
std::move将pair中的Foo移动到节点中,因此调用Foo(Foo &&)移动构造函数,输出对应日志。 - 临时的
std::pair<Foo, int>对象销毁,其内部的Foo被析构,输出~Foo(), val: 1。
转换过程中没有调用拷贝构造函数,是因为Foo到const Foo的转换只是添加const属性,没有创建新的Foo对象,仅改变了对象的类型修饰。
疑问2:为什么第一次insert调用Foo的移动构造函数,而第二次insert调用Foo的拷贝构造函数?
- 第一次insert传入的是
std::pair<Foo, int>临时对象(右值),转换为std::pair<const Foo, int>后仍是右值。此时pair中的Foo是可修改的右值,可以绑定到Foo&&参数,因此map内部移动构造节点中的Foo时,调用Foo(Foo &&)移动构造函数。 - 第二次insert传入的是
std::pair<const Foo, int>临时对象,其内部的Foo是const Foo类型的右值。const Foo右值无法绑定到Foo&&(移动构造函数的参数),因为无法移除const属性,因此只能匹配Foo(const Foo &)拷贝构造函数的参数,最终调用拷贝构造来创建节点中的Foo对象。
内容的提问来源于stack exchange,提问作者Rebased
相关产品推荐
相关产品推荐

