为什么STL map的[]运算符不是const?

107

为了提问而虚构的例子:

void MyClass::MyFunction( int x ) const
{
  std::cout << m_map[x] << std::endl
}

由于[]运算符是非const的,所以这段代码将无法编译。

这很不幸,因为[]语法看起来非常简洁。相反,我需要做类似这样的事情:

void MyClass::MyFunction( int x ) const
{
  MyMap iter = m_map.find(x);
  std::cout << iter->second << std::endl
}

这个问题一直让我困扰。为什么[]操作符是非const的?


5
如果给定的元素不存在,operator[] 应该返回什么? - Frerich Raabe
7
与成员函数 at 相同的是:抛出 std::out_of_range 异常。 - Jean-Simon Brochu
6个回答

101
对于 std::mapstd::unordered_map,如果索引值之前不存在,则 operator[] 会将其插入到容器中。虽然有点不直观,但就是这样。
由于必须允许失败并插入默认值,因此不能在容器的 const 实例上使用该运算符。

http://en.cppreference.com/w/cpp/container/map/operator_at


3
std::set 没有 operator[] - avakar
4
这是正确的答案,但一个const版本也可以像“at”成员函数一样执行相同的操作。这会抛出一个std::out_of_range异常... - Jean-Simon Brochu
3
在读取值时,没有默认值可提供。std::vector有一个读取操作符[],它是const的。map应该做同样的事情。 - wcochran
1
@Jean-SimonBrochu 在所有容器中的惯例是 at 可能会抛出异常,而 [] 不会。我认为没有必要打破这个惯例。 - Caleth

64

现在,使用C++11,您可以通过使用at()来获得更清晰的版本。

void MyClass::MyFunction( int x ) const
{
  std::cout << m_map.at(x) << std::endl;
}

5
如果map有const和非const的at()函数,为什么operator[]不也是一样呢? 它的const版本不插入任何值,而是抛出异常(或者在std::optional被加入到标准库时返回一个optional)。 - einpoklum
@einpoklum const正确性的重点主要是静态编译时检查。我宁愿让编译器抱怨,也不愿因为我没有正确使用const对象而抛出异常。 - Millie Smith
@einpoklum 很晚了,但是对于其他读者来说:拥有两个负载执行如此不同的操作将是可怕的。at有两种风味的唯一原因是它执行return *this;,而重载之间唯一的区别是返回引用的const性。两个at的实际效果完全相同(即没有效果)。 - HTNW

30

新读者注意事项。
原问题是关于STL容器(不特定于std::map)的。

需要注意的是,大多数容器都有operator []的const版本。
只是std::map和std::set没有const版本,这是由实现它们的底层结构所决定的。

来自std::vector

reference       operator[](size_type n) 
const_reference operator[](size_type n) const 

对于你的第二个示例,你也应该检查是否未找到元素而导致失败。

void MyClass::MyFunction( int x ) const
{
    MyMap iter = m_map.find(x);
    if (iter != m_map.end())
    {
        std::cout << iter->second << std::endl
    }
}

4
std::set 完全没有 operator[] - Everyone

2

由于operator[]可能会向容器中插入一个新元素,因此它不可能是const成员函数。请注意,operator[]的定义非常简单:m[k]相当于(*((m.insert(value_type(k, data_type()))).first)).second。严格地说,这个成员函数是不必要的:它只存在于方便之中。


0

索引运算符应该只对只读容器进行const限定(在STL中并不存在这样的容器)。

索引运算符不仅用于查看值。


8
问题是,为什么它没有两个重载版本 - 一个是const的,另一个是非const的,就像std::vector一样。 - Pavel Minaev

-2
如果您将std::map成员变量声明为可变的
mutable std::map<...> m_map;

在 const 成员函数中,您可以使用 std::map 的非 const 成员函数。


16
这个想法很糟糕。 - GManNickG
7
如果你这样做,你的类的API将是虚假的。该函数声称它是常量(const)——意思是它不会修改任何成员变量——但事实上它可能会修改m_map数据成员。 - Runcible
2
“mutable” 可用于像 std::mutex、缓存和调试助手之类的成员。如果要将 map 用作缓存以加速非常昂贵的 const “getter” 函数,则可以使用 mutable。你需要小心,但这本身并不是一个可怕的想法。 - Mark Lakata

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接