Элемент данных ссылочного типа обеспечивает "лазейку" вокруг const-correctness

Я недавно наткнулся на следующую "лазейку" вокруг const-correctness:

struct Inner {
  int field = 0;
  void Modify() {
    field++;
  }
};

struct Outer {
  Inner inner;
};

class MyClass {
public:
  Outer outer;
  Inner& inner; // refers to outer.inner, for convenience

  MyClass() : inner(outer.inner) {}

  void ConstMethod() const {
    inner.Modify();  // oops; compiles
  }
};

Кроме того, представляется возможным использовать эту лазейку для изменения объекта, объявленного как const, который, как я считаю, является неопределенным поведением:

int main() {
    const MyClass myclass;
    std::cout << myclass.outer.inner.field << "\n";  // prints 0
    myclass.ConstMethod();
    std::cout << myclass.outer.inner.field << "\n";  // prints 1
}

Это пугает меня, потому что кажется, что я только что вызвал неопределенное поведение, связанное с const-correctness в программе, которая не использует const_cast или отбрасывает константу, используя C-стиль.

Итак, мои вопросы:

  • Правильно ли я говорю, что вышеупомянутая программа имеет неопределенное поведение?
  • Если это так, это ошибка языка? Есть ли строка в вышеупомянутой программе, которая, возможно, не должна (может быть разумно сделана) не компилироваться?
  • Существуют ли какие-то рекомендации, которые следует соблюдать, чтобы избежать этой категории неопределенного поведения на практике?

Ответ 1

Любые изменения в const объекте - это неопределенное поведение, и этот сниппет действительно делает это.

Программа не плохо сформирована (для чего потребуется компиляционная ошибка), поскольку в момент инициализации inner, cv-квалификаторы еще не вступили в силу.

С точки зрения компилятора для выдачи предупреждения потребуется проанализировать все пути кода, ведущие к inner.Modify() и доказывая, что inner должна ссылаться на объект const, что невозможно в общем случае.

Лучшее предложение, вероятно, не будет иметь внутренних указателей/ссылок, которые в любом случае являются злыми.

Ответ 2

Это не ошибка в коде, помните, что const ссылается на самый внутренний элемент декларатора. В основном const на ConstMethod делает ссылку:

Inner& const inner;

Конечно, это не имеет никакого реального значения, потому что ссылки нельзя переопределить. Подумайте о том, чтобы сделать то же самое с указателем, и вы поймете, что внутреннее все еще можно изменить. Если бы:

Inner * const inner;

вы можете вызвать inner-> Изменить().

Ответ 3

Это не должно быть неопределенным поведением. Да, myclass.outer рассматривается как const внутри MyClass::ConstMethod() const, но он не начал его существование как const и, следовательно, его следует безопасно модифицировать с помощью ссылок non- const. Конечно, это может удивить читателей и/или пользователей вашего кода, поэтому вы должны стараться избегать этого.

Это аналогично

int x = 5;
int * xPtr = &x;
const int * constXPtr = &x;

в котором существование constXPtr не (и не должно) препятствовать модификации x через xPtr.

Стоит отметить, что такие возможности псевдонимов предотвращают много потенциальных оптимизаций компилятора.