In short: Does the following code have Undefined Behavior, or is this fine?
struct X
{
X(int b)
: value(b)
, ref(value)
{
}
int value;
int& ref;
void isThisUB() const
{
ref = 1;
}
};
int main()
{
const X x(2);
// Is either of these fine?
x.isThisUB();
x.ref = 3;
return x.value;
}
https://godbolt.org/z/1TE9a7M4a
X::value
is const for x
. According to my understanding of const semantics, this means that modifying it in any way is UB. Yet we can take a non-const reference to it in the constructor and then modify it through that, either in a const
member function or directly.
The C++ (at least 17) standard gives an example of const-related UB in [dcl.type.cv] that looks mostly the same, except it employs const_cast
. Note how p->x.j = 99
is denoted as UB. I do not see a fundamental difference between achieving this with const_cast
vs my above code.
So, is the code above UB? Are non-const reference members/pointers really this big of a footgun?
(If you can come up with search keywords that yield a related question and not just random const
stuff, I'll be mighty impressed.)
But const (int&) is a reference int& that is const , meaning that the reference itself cannot be modified.
No. A reference is simply an alias for an existing object. const is enforced by the compiler; it simply checks that you don't attempt to modify the object through the reference r .
The const member functions are the functions which are declared as constant in the program. The object called by these functions cannot be modified. It is recommended to use const keyword so that accidental changes to object are avoided. A const member function can be called by any type of object.
A const member function is a member function that guarantees it will not modify the object or call any non-const member functions (as they may modify the object). Now getValue() has been made a const member function, which means we can call it on any const objects.
Does the following code have Undefined Behavior, or is this fine?
It has UB. Standard says:
[dcl.type.cv]
Except that any class member declared mutable can be modified, any attempt to modify a const object during its lifetime results in undefined behavior.
x
is const and you modify its non-mutable member.
I do not see a fundamental difference between achieving this with const_cast vs my above code.
Indeed. Both are UB for the same reason.
Are non-const reference members/pointers really this big of a footgun?
The trigger for the footgun is the issue that the object is temporarily non-const while it is within its constructor. Hence pointers and references to non-const "this" and its subobjects are readily available wthin the constructor regardless of whether the object is going to be const or not. Thus we can conclude that storing those pointers/references for later use is ill-advised.
Storing pointers and references as members referring to "this" are a footgun for several other reasons as well. They require storage that's otherwise unnecessary if you were to access the referred member through its name directly. Furthermore, you'll find that the copy-semantics of the class will likely not be what you had in mind.
If you want to point to a member out of several alternatives, then use a member-pointer, not an object pointer / reference (using storage cannot be avoided for such case). This solves both copying and accidental const violation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With