Элемент данных ссылочного типа обеспечивает "лазейку" вокруг 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
.
Стоит отметить, что такие возможности псевдонимов предотвращают много потенциальных оптимизаций компилятора.