15

对于容器类,例如std::vector,有两个不同的常量概念:容器的概念(即它的大小)和元素的概念。似乎std::vector混淆了这两者,因此以下简单代码将无法编译:

struct A {
  A(size_t n) : X(n) {}
  int&x(int i) const { return X[i]; }    // error: X[i] is non-const.
private:
  std::vector<int> X;
};

请注意,即使调用 its 的数据成员(指向数据开始和结束的三个指针以及分配的缓冲区的结尾)std::vector没有更改,operator[]该成员不会const- 这不是一个奇怪的设计吗?

另请注意,对于原始指针,这两个常量概念被巧妙地分开,因此相应的原始指针代码

struct B {
  B(size_t n) : X(new int[n]) {}
  ~B() { delete[] X; }
  void resize(size_t n);                 // non-const
  int&x(int i) const { return X[i]; }    // fine
private:
  int*X;
};

工作得很好。

std::vector那么在使用(不使用)时处理这个问题的正确/推荐方法是什么mutable

const_cast<>一个

int&A::x(int i) const { return const_cast<std::vector<int>&>(X)[i]; }

被认为是可以接受的(X已知是 non- const,所以这里没有 UB)?

编辑只是为了防止进一步混淆:我确实想修改元素,即容器的内容,而不是容器本身(大小和/或内存位置)。

4

4 回答 4

23

C++ 只支持一个级别的const. 就编译器而言,它是按位常量:对象中的实际“位”(即计入sizeof)不能在不玩游戏(const_cast等)的情况下进行修改,但其他任何事情都是公平的游戏。在 C++ 的早期(1980 年代末,1990 年代初),关于按位 const 与逻辑 const(也称为 Humpty-Dumpty const,因为正如 Andy Koenig 曾经告诉我的那样,当程序员使用 const,它的意思正是程序员想要的意思)。共识最终一致支持逻辑常量。

这确实意味着容器类的作者必须做出选择。容器的元素是容器的一部分,还是不是。如果它们是容器的一部分,那么如果容器是 const,它们就不能被修改。没有办法提供选择;容器的作者必须选择其中一个。在这里,似乎也有一个共识:元素是容器的一部分,如果容器是 const 的,它们就不能被修改。(也许与 C 样式数组的平行在这里起了作用;如果 C 样式数组是 const,那么您不能修改它的任何元素。)

和你一样,我也曾遇到过想要禁止修改向量大小(也许是为了保护迭代器)而不是元素大小的情况。没有真正令人满意的解决方案;我能想到的最好的方法是创建一个包含 a 的新类型,并提供与 我在这种特定情况下需要mutable std::vector的含义相对应的转发功能。const如果要区分三个级别(完全 const、部分 const 和非常量),则需要推导。基类仅公开完全 const 和部分 const 函数(例如 aconst int operator[]( size_t index ) const;int operator[]( size_t index );,但不公开void push_back( int );); 允许插入和删除元素的函数仅在派生类中公开。不应插入或删除元素的客户端仅传递对基类的非常量引用。

于 2013-02-11T16:43:23.837 回答
4

不幸的是,与指针不同,你不能做类似的事情

std::vector<int> i;
std::vector<const int>& ref = i;

这就是为什么std::vector不能在这两种const可能适用的情况下消除歧义,而且必须保守。我个人会选择做类似的事情

const_cast<int&>(X[i]);

编辑:正如另一位评论者准确指出的那样,迭代器确实模拟了这种二分法。如果您将 a 存储vector<int>::iterator在开头,则可以在 const 方法中取消引用它并取回 non-const int&。我认为。但是你必须小心失效。

于 2013-02-11T16:19:26.537 回答
3

这不是一个奇怪的设计,这是一个非常深思熟虑的选择,而且是正确的恕我直言。

你的B例子不是一个很好的类比 a std::vector,一个更好的类比是:

struct C {
   int& get(int i) const { return X[i]; }
   int X[N];
};

但有一个非常有用的区别是可以调整数组的大小。上面的代码由于与原始代码相同的原因无效,数组(或vector)元素在概念上是包含类型的“成员”(技术上的子对象),因此您不应该能够通过const成员函数修改它们。

我会说这const_cast是不可接受的,mutable除非作为最后的手段,否则也不会使用。您应该询问为什么要更改 const 对象的数据,并考虑将成员函数设为非 const。

于 2013-02-11T16:18:28.920 回答
-2

我建议使用std::vector::at()方法而不是const_cast.

于 2013-02-11T16:39:49.760 回答