const vs非容器及其内容的const

Wal*_*ter 12 c++ containers const

对于容器类,例如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;
};
Run Code Online (Sandbox Code Playgroud)

需要注意的是,即使数据成员(三个指针指向数据的开始和结束,所分配的缓冲区的结束)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;
};
Run Code Online (Sandbox Code Playgroud)

工作得很好.

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

是一个const_cast<>如此

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

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

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

Jam*_*nze 18

C++只支持一个级别const.就编译器而言,它是按位const:实际在对象中的"位"(即计入sizeof)不能在没有玩游戏(const_cast等等)的情况下进行修改,但其他任何东西都是公平的游戏.在C++的早期(20世纪80年代末,90年代初),关于按位const与逻辑const(也称为Humpty-Dumpty const)的设计优势进行了大量讨论,因为正如Andy Koenig曾告诉我的那样,程序员使用 const,这意味着程序员想要它的意思.最终的共识有利于逻辑const.

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

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

  • @JamesKanze:解释非常好,但是最顶部的前两个开头句子给人的印象是按位 const 是 C++ 支持的。*(我希望没有太多 C++ 程序员是 TL;DR。)* 这句话在编译器后端是正确的,但前端只认为 `const` 作为语法辅助 - 它是用于类型检查,仅此而已。(顺便说一下,C++11 引入了编译时 `constexpr`。)逻辑常量只存在于程序员的大脑中。当 C++ 程序员开始多线程编程时就会意识到其中的差异。 (2认同)