You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

std::unordered_map插入与遍历对象差异:拷贝构造函数为何未被调用?

为什么std::unordered_map插入的对象和遍历得到的不是同一实例?

用户问题描述

使用std::unordered_map时发现,插入的对象与通过范围遍历获取的对象并非同一实例。怀疑存在对象拷贝行为,但为自定义类的拷贝构造函数添加日志输出后,发现它并未被调用。想了解插入和遍历std::unordered_map的过程中底层究竟发生了什么。

用户提供的输出结果

mypair constuctor
mypair constuctor
string1 address:0x7ffccb813ba0
string2 address:0x7ffccb813bd0
++++++++++++++++
auto &x address:0x55fb40529378
string2: 0.5
auto &x address:0x55fb405292c8
string1: 0.3
++++++++++++++++
auto x address:0x7ffccb813c00
string2: 0.5
auto x address:0x7ffccb813c00
string1: 0.3
++++++++++++++++

用户提供的源代码

#include <iostream>
#include <string>
#include <unordered_map>

class mypair : public std::pair<std::string,double> {
public:
      mypair(std::string str, double num):std::pair<std::string,double>(str, num) {
          std::cout<<"mypair constuctor"<<std::endl;
      }
      mypair( const mypair& ) {
          std::cout<<"mypair copy constuctor"<<std::endl;
      }
      mypair& operator=(const mypair&) {
          std::cout<<"mypair copy assignment"<<std::endl;
          return *this;
      }
};

int main ()
{
    std::unordered_map<std::string,double> myMap;
    mypair string1 ("string1", 0.3);
    mypair string2 ("string2", 0.5);
    std::cout << "string1 address:" << &string1 << std::endl;
    std::cout << "string2 address:" << &string2 << std::endl;
    std::cout <<"++++++++++++++++"<< std::endl;
    myMap.insert (string1);
    myMap.insert (string2);
    
    for (auto& x: myMap) {
        std::cout << "auto &x address:"<< &x << std::endl<< x.first << ": " << x.second << std::endl;
    }
    std::cout <<"++++++++++++++++"<< std::endl;
    
    for (auto x: myMap) {
        std::cout << "auto x address:"<< &x << std::endl<< x.first << ": " << x.second << std::endl;
    }
    std::cout <<"++++++++++++++++"<< std::endl;
    return 0;
}

问题分析与解答

核心矛盾:存储类型不匹配

你定义的std::unordered_map<std::string, double>内部存储的是std::pair<const std::string, double>类型的元素,而非你自定义的mypair类。这是所有现象的根源。

插入过程的底层逻辑

当执行myMap.insert(string1)时:

  1. 隐式类型转换:因为mypair继承自std::pair<std::string, double>,编译器会自动将mypair对象转换为std::pair<const std::string, double>(map的key必须是const,避免修改后破坏哈希结构)。
  2. 未触发自定义拷贝构造:这个转换过程调用的是std::pair的拷贝构造函数(从非const的std::pair<std::string, double>拷贝到const版本),而非你写的mypair::mypair(const mypair&),所以你看不到自定义拷贝构造的日志。
  3. 内存位置差异:转换后的std::pair<const std::string, double>会被存储到unordered_map的堆内存节点中,而你定义的string1/string2在栈上,自然地址完全不同。

遍历过程的细节

  1. 引用遍历(auto& x)
    • x是对unordered_map堆内存中std::pair<const std::string, double>对象的直接引用,所以输出的地址是堆上的不同节点地址,和栈上的mypair无关。
  2. 值遍历(auto x)
    • x是栈上的临时变量,每次循环会把map内部的元素拷贝到这个临时变量里。循环结束后临时变量会被销毁,下一次循环复用栈上的同一位置,所以两次输出的地址完全相同。

验证输出现象

  • 栈地址(如0x7ffccb813ba0)和堆地址(如0x55fb40529378)的范围差异,正好对应原始对象和map内部对象的存储位置。
  • 值遍历的地址固定,完全符合临时变量复用栈空间的特性。

修正建议

如果希望unordered_map存储你自定义的mypair,需要调整map的类型。例如,将mypair作为值类型:

// 调整map类型,值为mypair
std::unordered_map<std::string, mypair> myMap;

此时需要确保mypair满足map值类型的要求(可拷贝、可移动等),同时可能需要调整插入逻辑以适配新的类型。


内容的提问来源于stack exchange,提问作者Chansy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 09:04:56