关于const 引用和const 指针的弱语义

问题描述

这可能已经被问过了。

为什么允许将const 引用分配给非常量变量?

为什么允许这样做

int mut {0};
const int & r_to_c {mut};
mut = 1;
// Now r_to_c changed to 1!
// But it was supposed to be a reference to something constant!

当然,我不能改变引用到常量本身的值。我不能

r_to_c = 2;

但是 const 限定条件是不是执行得太少了?我希望,从 const-ness 的承诺来看,不允许绑定到可变变量。

否则const会给我什么保证?他们看起来很弱,似乎很容易诱使程序员用脚开枪。

我知道 C++ 以允许人们用脚射击自己而闻名。我对允许危险的事情没有意见。在这种情况下,我的问题是,鉴于此处 const 的语义不是人们所期望的,在这种情况下它似乎是故意欺骗。

我的问题是关于编译器和语言语义,而不是特别是关于引用(我可以使用分配给非常量变量地址的指向常量的指针来问同样的问题。像 { {1}})。

为什么const 引用(或const 指针)的语义只是“你不能使用这个特定的窗口来修改你看到的东西(但如果你有其他窗口非常量,您可以修改它)”而不是更强大的“这只能是声明为常量并且编译器保证它保持不变的东西的窗口”?


[术语注意:我使用表达式“const 引用”而不是“const 引用”,因为“const 引用”被解释为 int mut{0}; const int * p_to_c{&mut}; - 与调用 T& const 一致const 指针”-,不存在。]

解决方法

但是 const 限定是不是执行得太少了?我希望,从 const-ness 的承诺来看,不允许绑定到可变变量。

不,这不是“太少”。你期待错了。

首先,是否绑定了const引用并不会使对象本身const。那会很奇怪:

void foo(int& x) {
    static const int& y = x;
}

当我调用 foo 时:

int x = 42;
foo(x);

我不知道其他人是否会保留对我的非常量 constx 引用。

否则,const 会给我什么保证?

您不能通过 const 引用修改某些内容:

void bar(const int& x);
int x = 0;
bar(x);

当我调用一个带 const& 的函数时,我知道它不会修改我的(非常量)参数。如果 const 引用不会绑定到非常量对象,那么就没有办法使最后一个示例工作,即您只能将非常量对象传递给修改它们的函数,但不能传递给不修改它们的函数。

附言我能理解你的困惑。有时会忽略持有常量引用并不意味着不能修改对象。考虑这个例子:

#include <cstddef>
#include <iostream>

struct foo {
    const int& x;
};

int main() {
    int y = 0;
    foo f{x};
    std::cout << f.x;   // prints 0
    y = 42;
    std::cout << f.x;   // prints 42
}

将成员的值打印到屏幕会产生两种不同的结果,即使 foo::x 是一个常量引用!它是“常量引用”而不是“常量引用”。 const 在这里的实际含义是:您不能通过 y 修改 f.x

,

将常量引用绑定到可变变量的能力实际上是语言中非常有价值的特性。考虑一下我们可能想要一个可变变量。

int mut {0};
// ... some time later
mut = 1;

这是完全合理的;它是一个在程序执行过程中会发生变化的变量。

现在假设我们想打印这个变量的值,并想写一个函数来做这个。

void print(int param)  // or 'int &' to avoid a copy,// but the point here is that it's non-const 
{
  std::cout << param;
}

这很好,但显然函数没有改变参数。我们希望强制执行此操作,以便编译器捕获 param = 42; 之类的错误。为此,我们将 param 设为 const & 参数。

void print(int const & param);

如果我们不能用非常量的参数调用这个函数,那将是非常不幸的。毕竟,我们不关心参数可能在函数外被修改。我们只想说参数保证不会被 print 修改,而将 const & 绑定到可变变量正是为了这个目的。

,

对 const 对象的引用与对非 const 对象的 const 引用不同,但 C++ 的类型系统不区分它们。

这有点违反 LSP;它与对可变正方形和矩形的引用有同样的问题。

您可以创建“真正的常量”,但在声明时需要帮助。

template<class T>
struct true_const {
  const T value;
};

true_const<int>&true_const<T> const& 可以作为参考传递,并且没有人可以在“背后”编辑它(无需调用 UB)。

当然,采用真正 const 的函数不能也采用普通对象。

void bob( true_const<int>& x ) {
  auto local = x.value;
  call_some_other_function();
  assert(local == x.value); // guaranteed to be true
}

void bob( const int& x ) {
  auto local = x;
  call_some_other_function();
  assert(local == x); // NOT guaranteed to be true
}

const 类中的字段是真正的常量;修改它们是未定义的行为。

围绕类中的 const 类型的瘦包装器因此保证数据为 const。然后参考那个。

现在,true_const 可以使用一些运算符支持。

template<class T>
struct true_const {
  const T value;
  constexpr T const& get() const { return value; }
  constexpr T const& operator*() const { return get(); }
  constexpr T const* operator->() const { return std::addressof(value); }
  constexpr operator T const&() const { return get(); }

  // concepts-defended operator+,==,etc
};