【问题标题】:Why isn't vector::operator[] implemented similar to map::operator[]?为什么 vector::operator[] 的实现不类似于 map::operator[]?
【发布时间】:2016-08-30 16:24:07
【问题描述】:

std::vectoroperator[] 是否有任何理由只返回引用而不是插入新元素? vector::operator 的 cppreference.com 页面显示 here

std::map::operator[] 不同,此运算符从不向容器中插入新元素。

当页面为map::operator[]says

“返回对映射到与 key 等效的键的值的引用,如果这样的键不存在,则执行插入。”

为什么vector::operator[] 不能像map::operator[] 调用insert(std::make_pair(key, T())).first->second; 那样通过调用vector::push_backvector::insert 来实现?

【问题讨论】:

  • std::map::operator[] 的实际实现方式已经引起了足够多的混乱。 UB 适合std::vector 越界访问。
  • 想象一下,如果我们写 some_vector[1000]some_vector 的大小为 10 会发生什么。好吧,我只是不知道中间有 990 个条目会发生什么。
  • @NickyC:但是地图也有同样的问题!
  • @KerrekSB,怎么会这样?地图中没有要初始化的中间条目。
  • 我认为问题的区别在于,为什么std::map::operator[]() 插入一个元素,即使只是访问一个不存在的键?

标签: c++ dictionary vector stl


【解决方案1】:

很简单:因为它没有意义。你期待什么

std::vector<int> a = {1, 2, 3};
a[10] = 4;

做什么?即使您指定了索引 10,也要创建第四个元素?创建元素 3 到 10 并返回对最后一个的引用?两者都不是特别直观。

如果你真的想用operator[]而不是push_back来填充向量,你可以在设置之前调用向量上的resize来创建元素。

编辑:或者,如果您真的想要一个关联容器,其中除了排序之外索引很重要,std::map&lt;int, YourData&gt; 实际上可能更有意义。

【讨论】:

  • 可能值得强调关键点:对于map,操作员最多在给定键处插入一个元素。对于vector,您还必须在其他索引处插入 other 元素。
  • +1。还值得一提的是,C++ 比许多语言(例如 Java)更多地考虑性能问题。这就是vector::operator[] 不执行边界检查的原因;如果您觉得需要它们,您可以手动选择加入它们,但 C++ 不希望在这里有任何开销。即使在向量中插入不存在的元素是有意义的,它也会直接与性能目标相矛盾。与希望进行边界检查一样,如果您希望为您插入元素,您可以自己在向量之上构建一个更高级别的类并选择加入。
  • @GManNickG 所以基本上,如果我想插入不存在的元素,那么我不得不使用地图,因为向量是一个序列容器?
  • @1337ninja:不。就像我写的那样,您可以使用resizepush_back 代替std::vector。这真的取决于你的用例,你还没有很好地解释它。
【解决方案2】:

地图和矢量是完全不同的概念。地图是“关联容器”,而矢量是“序列容器”。描述差异超出了此答案的范围,尽管在最肤浅的层面上,映射通常实现为红黑树,而向量是 C 样式数组的卷积包装(元素连续存储在记忆)。

如果你想检查一个元素是否已经存在,你需要调整整个容器的大小。但是,如果您决定删除该元素会发生什么?您如何处理刚刚创建的条目?有地图:

std::map<int, int> m; 
m[1] = 1; 
m.erase(m.begin());

这是一个恒定的操作。

带有向量:

std::vector<int> v;
// ... initialize some values between 25 and 100
v[100] = 1;
v.erase(v.begin() + 25, v.end());

这是一个线性运算。这对于地图来说是非常低效的(相对而言)。虽然这是一个人为的例子,但不难想象这在其他情况下会如何爆发。至少,大多数人会竭尽全力避免operator[],这本身就是一种成本(维护和代码复杂性)。

【讨论】:

  • +1 因为您提到了向量和指针基于两个独立的数据结构这一事实。向量是非常花哨的数组,而映射是基于指针的。如果您需要使用迭代器进行搜索,地图非常有用。如果你知道数组中需要什么元素,Vectors 就很棒。
【解决方案3】:

std::vector 的 operator[] 是否有任何理由只返回一个引用而不是插入一个新元素?

std::vector::operator[] 以类似数组的方式实现,因为std::vector 是一个序列容器(即类似数组)。整数类型的标准数组不能越界访问。同样,也不允许使用超出向量长度的索引访问std::vector::operator[]。所以,是的,它没有像你问的那样实现的原因是因为在其他情况下,C++ 中的数组不会像那样行事。

std::map::operator[] 不是序列容器。它的语法使其类似于其他语言中的关联数组。就 C++(及其前身 C)而言,map::operator[] 只是语法糖。它是operator[] 家族的“害群之马”,不是std::vector::operator[]

C++ 规范中有趣的部分是使用不存在的键访问映射,使用std::map::operator[]向映射添加一个元素。因此,

#include <iostream>
#include <map>
int main(void) {
   std::map<char, int> m;
   m['a'] = 1;
   std::cout << "m['a'] == " << m['a'] << ", m.size() == " << m.size() << std::endl;
   std::cout << "m['b'] == " << m['b'] << ", m.size() == " << m.size() << std::endl;
}

结果:

m['a'] == 1, m.size() == 1
m['b'] == 0, m.size() == 2

另见:Difference between map[] and map.at in C++?

[map::at] 如果键不存在则抛出异常,find 如果元素不存在则返回aMap.end()operator[] value-initializes 一个新的如果相应键不存在值,则为相应键的值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-21
    • 2018-06-14
    • 1970-01-01
    • 2011-07-18
    • 1970-01-01
    • 2013-11-05
    • 1970-01-01
    相关资源
    最近更新 更多