让std::map::operator[]变聪明

本文探讨了std::map::operator[]的局限性,并提出了一种改进方案,通过引入ValueWrapper类和TypeWrapper模板来实现更加智能的行为,能够在必要时插入新值,而在其他情况下仅进行查找。

std::map::operator[]是一个设计得很磋却很好用的接口,看下面的例子:

 

#include <map>

#include <iostream>

 

using namespace std;

 

int main()

{

  map<int, int> iiMap;

  iiMap[1] = 2; ---------------------------(1)

  iiMap[2]; --------------------------------(2)

  cout << iiMap[1] << iiMap[2] << iiMap[3] << endl;

  return 0;

}

 

operator[]的语义是说:我先查一下这个key,如果没有就先插入一个pair(key, Value()),然后把value的引用返回。

 

当(2)这样用时,它也会插入一对值(2,0),实际上你可能不希望这么做,可能你只是想看看key=2的值是多少或者存不存在?是的,它没有意识到何时需要,一定需要插入新值。显然,当[]作左值时需要,作右值时只要查一下就够了。

 

以自己的一个hash_table类为例,实现就省了,它有2个不磋的接口:

 

template<typename Key, typename Value, ...>

class hash_table

{

public:

  Value* Get_Value(const Key& key);

  Value* Set_Value(const Key& key, const Value& value);

...

};

 

为了实现更好的operator[],这个接口被设计成:

 inline ValueWrapper operator[](const Key& key)
 {
  return ValueWrapper(key, Get_Value(key), this);
 }

 

ValueWrapper是一个托管类,它是Value的一个包装:

 class ValueWrapper
 {
  Key     key;
  Value*    pValue;
  hash_table_impl* mother;

 public:
  ValueWrapper(const Key& _key, Value* _pValue, hash_table_impl* _mother) :
  key(_key), pValue(_pValue), mother(_mother)
  {
  }

  ...

 };

 

它再重载operator=和operator Value,如下:

  ...

  operator Value()
  {
   if (pValue)
   {
    return *pValue;
   }
   else
   {
    return Value();
   }
  }

  ValueWrapper& operator=(const Value& value)
  {
   if (pValue)
   {
    *pValue = value;
   }
   else
   {
    mother->Set_Value(key, value);
   }
   return *this;
  }
 };

这样基本功能就完成了,它能感知=运算符,同时不失一般的在一些地方被隐式转化成Value。

 

如此还不够,像+= -= ++ --这些运算符也能代表着必须是左值,你如果一个一个重载,很麻烦,再写一个一般的包装类:

template<typename T>
class TypeWrapper
{
public:
 explicit TypeWrapper(T* pReff = NULL) : m_pReff(pReff)
 {
 }

 virtual ~TypeWrapper()
 {
 }

 ...

protected:
 virtual T& get_lvalue()
 {
  return *m_pReff;
 }

 virtual T get_rvalue()
 {
  if (m_pReff)
  {
   return *m_pReff;
  }
  else
  {
   return T();
  }
 }
};

 

提供2个虚接口,他们分别是取左值和取右值,然后把所有赋值运算符++,--和operator T都重载好(使用宏减少重复性代码),如此再由它继承出前面的ValueWrapper,这样更简单,这样一个聪明的operator[]完成,它能在需要的时候insert一个新值,而其它时间都是在查看。

<think>我们被要求比较std::unordered_mapstd::map(通常基于红黑树实现)。两者都是关联容器,但有不同的特性和性能特征。 我们将从以下几个方面进行对比: 1. 内部实现 2. 时间复杂度 3. 元素顺序 4. 内存使用 5. 迭代器有效性 6. 使用场景 然后给出代码示例说明基本操作和注意事项。 </think>### `std::unordered_map` vs `std::map` 全面对比 在 C++ 中,`std::unordered_map` 和 `std::map` 都是关联容器,但在实现、性能特性和使用场景上有显著差异: --- #### 1. **底层实现** | 容器 | 实现原理 | 结构特点 | |--------------------|--------------------------|--------------------------| | `std::map` | **红黑树**(自平衡二叉搜索树) | 节点存储键值对 + 左右子节点指针 | | `std::unordered_map` | **哈希表**(桶数组 + 链表/红黑树) | 数组存储桶头指针 + 链表存储冲突元素 | ```cpp // map: 树状结构示意 struct TreeNode { std::pair<Key, Value> data; TreeNode* left; TreeNode* right; // ... 平衡信息 }; // unordered_map: 哈希桶结构示意 std::vector<Bucket> buckets; // 桶数组 struct Node { std::pair<Key, Value> data; Node* next; // 冲突链表 }; ``` --- #### 2. **时间复杂度对比** | 操作 | `std::map` | `std::unordered_map` | |-------------------|-----------------|----------------------| | 插入 (`insert`) | O(log n) | **O(1)** 平均 / O(n) 最坏 | | 查找 (`find`) | O(log n) | **O(1)** 平均 / O(n) 最坏 | | 删除 (`erase`) | O(log n) | **O(1)** 平均 / O(n) 最坏 | | 范围遍历 | **O(n)** | O(n) 但缓存不友好 | | 有序遍历 | **有序迭代** | 无序 | --- #### 3. **元素顺序特性** | 特性 | `std::map` | `std::unordered_map` | |-----------------------|--------------------------------|--------------------------------| | 键排序 | 严格按**键升序**(默认`<`比较) | **无任何顺序保证** | | 迭代顺序 | 稳定的中序遍历顺序 | 依赖哈希函数和桶布局 | | 自定义排序 | 支持自定义比较器 | 不支持 | ```cpp // map 保证有序输出 std::map<int, std::string> m{{3, "C"}, {1, "A"}, {2, "B"}}; for (auto& [k, v] : m) std::cout << k; // 输出: 1 2 3 // unordered_map 无序输出 std::unordered_map<int, std::string> um{{3, "C"}, {1, "A"}, {2, "B"}}; for (auto& [k, v] : um) std::cout << k; // 可能输出: 2 1 3 或其他顺序 ``` --- #### 4. **内存与性能考量** | 因素 | `std::map` | `std::unordered_map` | |---------------------|------------------------------|-------------------------------| | 内存开销 | 每个节点额外 2 指针 + 平衡信息 | 桶数组 + 每个元素链表指针 | | 缓存友好度 | 较差(节点分散) | **更差**(哈希冲突时跳跃访问) | | 哈希函数要求 | 不需要 | 需要高质量哈希函数 (`std::hash`) | | 自定义哈希支持 | 不支持 | 支持自定义哈希器 | --- #### 5. **迭代器有效性** | 操作 | `std::map` | `std::unordered_map` | |---------------------|----------------------------------|----------------------------------| | 插入元素 | **不失效**(除被修改节点) | 可能导致**全部重哈希**(桶扩容) | | 删除元素 | 仅使被删元素迭代器失效 | 仅使被删元素迭代器失效 | --- ### **关键选择指南** 1. 使用 `std::map` 当: - 需要**有序遍历**或**范围查询**(如 `lower_bound`) - 键类型无高效哈希函数 - 避免哈希冲突导致的性能波动 - 内存限制较小(哈希表桶数组有最小开销) 2. 使用 `std::unordered_map` 当: - 需要**O(1)平均访问性能** - 不关心元素顺序 - 有高质量哈希函数(或能自定义) - 内存充足(哈希表需要额外 20-50% 空间) --- ### **自定义键示例** ```cpp // 自定义键类型(需支持map比较或unordered_map哈希) struct Point { int x, y; bool operator==(const Point& p) const { return x == p.x && y == p.y; } }; // 为map定义比较器 struct PointCompare { bool operator()(const Point& a, const Point& b) const { return std::tie(a.x, a.y) < std::tie(b.x, b.y); } }; std::map<Point, std::string, PointCompare> pointMap; // 为unordered_map定义哈希器 struct PointHash { size_t operator()(const Point& p) const { return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1); } }; std::unordered_map<Point, std::string, PointHash> pointUMap; ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值