const vs非容器及其内容的const

时间:2013-02-11 16:07:09

标签: c++ const 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::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?

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

4 个答案:

答案 0 :(得分:17)

C ++仅支持一级const。至于编译器 关注的是,它是按位const:实际上是“位” 如果没有,则无法修改对象(即在sizeof中计算) 玩游戏(const_cast等),但其他任何事情都是公平的 游戏。在C ++的早期(20世纪80年代末,90年代初) 对于按位的设计优势进行了大量讨论 const与逻辑const(也称为Humpty-Dumpty const, 因为正如Andy Koenig曾经告诉我的那样,当程序员使用时 const,这意味着程序员想要它的意思。 最终的共识有利于逻辑const。

这确实意味着容器类的作者必须制作 一个选择。是容器的元素的一部分 容器,或不。如果他们是容器的一部分,那么他们 如果容器是const,则无法修改。没有办法 提供选择;容器的作者必须选择一个或 另一个。在这里,似乎有一个共识: 元素是容器的一部分,如果容器是 const,它们无法修改。 (也许与...平行 C风格阵列在这里发挥了作用;如果C样式数组是const, 然后你不能修改它的任何元素。)

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

答案 1 :(得分:4)

不幸的是,与指针不同,你不能做像

这样的事情
std::vector<int> i;
std::vector<const int>& ref = i;

这就是为什么std::vector无法消除它们可能适用的两种const之间的歧义,而且它必须是保守的。我个人会选择做像

这样的事情
const_cast<int&>(X[i]);

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

答案 2 :(得分:3)

这不是一个奇怪的设计,它是一个非常慎重的选择,而且是正确的恕我直言。

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

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

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

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

答案 3 :(得分:-1)

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