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

const vs non-const of container and its content

本文关键字:常量 非常      更新时间:2023-10-16

对于容器类,如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数据成员(指向数据的开始和结束以及分配的缓冲区的结束的三个指针)被而不是通过对其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是非const,所以这里没有UB)?

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

C++只支持一个级别的const。就编译器而言有关,它是按位常量:对象(即在sizeof中计数)在没有玩游戏(const_cast等),但其他任何事情都是公平的游戏在C++的早期(20世纪80年代末、90年代初)关于bitwise的设计优势进行了大量讨论const与逻辑const(也称为Humpty Dumpty const,因为正如Andy Koenig曾经告诉我的那样,当程序员使用const,它的意思正是程序员希望它的意思)。一致意见最终一致支持逻辑常量。

这意味着容器类的作者必须选择。容器的元素是容器。如果它们是容器的一部分,那么它们如果容器为常量,则无法修改。没有办法提供选择;容器的作者必须选择另一个。在这里,似乎也有一个共识:元素是容器的一部分,如果容器const,它们不能被修改。(也许与C型数组在这里发挥了作用;如果C型数组是const,则不能修改它的任何元素。)

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

不幸的是,与指针不同,您不能执行类似的操作

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

这就是为什么std::vector不能在这两种可能适用的const之间消除歧义,而且它必须是保守的。I、 就个人而言,会选择做一些类似的事情

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

编辑:正如另一位评论者准确指出的那样,迭代器do为这种二分法建模。如果将vector<int>::iterator存储到开头,那么可以在const方法中取消引用它,并返回一个非常量的int&。我想。但你必须小心无效。

这不是一个奇怪的设计,这是一个非常深思熟虑的选择,也是正确的选择。

您的B示例不是std::vector的好类比,更好的类比是:

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

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

我想说const_cast是不可接受的,除非万不得已,否则也不能使用mutable。您应该询问为什么要更改const对象的数据,并考虑将成员函数设置为非const。

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