C++ 容器及其内容的常量与非常量

C++ 容器及其内容的常量与非常量,c++,constants,containers,C++,Constants,Containers,对于容器类,例如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; }; 很好用 那么,在使用std::ve

对于容器类,例如
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;
};
很好用

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

是一个
const\u cast
,如中所示

int&A::x(int i) const { return const_cast<std::vector<int>&>(X)[i]; }
int&A::x(inti)const{return const_cast(x)[i];}
视为可接受(
X
已知为非常量,因此此处无UB)


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

这不是一个奇怪的设计,这是一个非常慎重的选择,而且是正确的选择

对于
std::vector
,您的
B
示例不是一个很好的类比,更好的类比是:

struct C {
   int& get(int i) const { return X[i]; }
   int X[N];
};
但是非常有用的区别是数组可以调整大小。上述代码无效的原因与原始代码相同,即数组(或
vector
)元素在概念上是包含类型的“成员”(技术上是子对象),因此您不能通过
const
成员函数修改它们


我想说,const_cast是不可接受的,除非作为最后手段,否则也不能使用
mutable
。你应该问为什么要改变一个const对象的数据,并考虑让成员函数非const .< /p> 不幸的是,不像指针,你不能做像

之类的事情。
std::vector<int> i;
std::vector<const int>& ref = i;
const_cast<int&>(X[i]);

编辑:正如另一位评论者准确指出的那样,迭代器确实模拟了这种二分法。如果在开头存储了一个
vector::iterator
,则可以在const方法中反引用它,并返回一个非const
int&
。我想。但是您必须小心无效。

我建议使用
std::vector::at()
方法,而不是
const\u cast

C++只支持一级
const
。至于编译器 值得注意的是,它是按位常量:“位”实际上在 对象(即计数在
sizeof
中)在没有 玩游戏(
const_cast
等),但其他一切都是公平的 游戏在C++早期(20世纪80年代末,90年代初) 关于bitwise的设计优势进行了大量讨论 常量vs.逻辑常量(也称为Humpty-Dumpty常量, 因为正如Andy Koenig曾经告诉我的,当程序员使用
const
,它的意思正是程序员想要它的意思)。 一致意见最终合并为支持逻辑常数

这确实意味着容器类的作者必须 选择。容器的元素是 容器,或不是。如果它们是容器的一部分,那么它们 如果容器为常量,则无法修改。没有办法 提供选择;容器的作者必须选择一个或多个 其他的。在这方面,似乎也有一个共识:即 元素是容器的一部分,如果容器是 常量,它们不能被修改。(也许与之类似 C样式数组在这里起作用;如果C样式数组是常量, 则无法修改其任何元素。)

和你一样,我也遇到过想禁止的时候 修改载体的大小(可能是为了保护 迭代器),但不是它的元素。真的没有 满意的解决方案;我能想到的最好办法就是创造 一种新类型,它包含一个
可变std::vector
,并提供 与
const
在这种特殊情况下,我需要。如果你想区分 三个级别(完全常量、部分常量和非常量), 你需要推导。基类只公开 完全常量和部分常量函数(例如a
const
整数运算符[](大小索引)常量;
整数运算符[](
大小(索引);
,但不是
void push_-back(int);
);这个 允许插入和删除元素的函数包括 仅在派生类中公开。不应该的客户 插入或删除元素仅传递一个非常量引用
到基类。

可以通过调用
操作符[]
来更改
std::vector
的数据。正如您所写的
A::X
A.X(1)+
是完全合法的,并修改向量的内容。@DavidSchwartz向量的内容不是它的实际数据(尽管您可以在逻辑上将它们关联起来)。如果检查
std::vector
,它只有3个指针作为数据(数据的开始和结束以及缓冲区的结束)。这些都没有改变,这是一个解释问题。如果你把一个
向量
看作是一个可重新调整大小的C数组,那么我同意。但是,
resize()
etc函数在哪里适合这种类比?它们必须不止是常量:您希望能够为元素提供非常量访问权限,但不能为大小提供非常量访问权限。这在
std::vector
中是不可能的。我认为解决这一问题的通常方法是提供迭代器,迭代器通常允许更改内容,但不允许更改容器。这只是一个类比,不要太字面化,反正
resize
不是常量,所以允许对对象进行变异。我不确定你说的迭代器是什么意思,但是没有一个标准容器会给你一个常量容器的非常量迭代器。我说的迭代器是,如果你有一个(非常量)迭代器,你就不能修改容器。迭代器的使用提出了一个有趣的解决方案:一个视图类,它只包含开始迭代器和结束迭代器,只提供有限的函数
const_cast<int&>(X[i]);