I understand that having a const
method in C++ means that an object is read-only through that method, but that it may still change otherwise.
However, this code apparently changes an object through a const
reference (i.e. through a const
method).
Is this code legal in C++?
If so: Is it breaking the const
-ness of the type system? Why/why not?
If not: Why not?
Note 1: I have edited the example a bit, so answers might be referring to older examples.
Edit 2: Apparently you don't even need C++11, so I removed that dependency.
#include <iostream>
using namespace std;
struct DoBadThings { int *p; void oops() const { ++*p; } };
struct BreakConst
{
int n;
DoBadThings bad;
BreakConst() { n = 0; bad.p = &n; }
void oops() const { bad.oops(); } // can't change itself... or can it?
};
int main()
{
const BreakConst bc;
cout << bc.n << endl; // 0
bc.oops(); // O:)
cout << bc.n << endl; // 1
return 0;
}
Update:
I have migrated the lambda to the constructor's initialization list, since doing so allows me to subsequently say const BreakConst bc;
, which -- because bc
itself is now const (instead of merely the pointer) -- would seem to imply (by Stroustrup) that modifying bc
in any way after construction should result in undefined behavior, even though the constructor and the caller would have no way of knowing this without seeing each others' definitions.
The issue is one of logical const versus bitwise const. The compiler doesn't know anything about the logical meaning of your program, and only enforces bitwise const. It's up to you to implement logical const. This means that in cases like you show, if the pointed to memory is logically part of the object, you should refrain from modifying it in a const function, even if the compiler will let you (since it isn't part of the bitwise image of the object). This may also mean that if part of the bitwise image of the object isn't part of the logical value of the object (e.g. an embedded reference count, or cached values), you make it
mutable
, or even cast away const, in cases where you modify it without modifying the logical value of the object.The const feature merely helps against accidental misuse. It is not designed to prevent dedicated software hacking. It is the same as private and protected membership, someone could always take the address of the object and increment along the memory to access class internals, there is no way to stop it.
So, yes you can get around const. If nothing else you can simply change the object at the memory level but this does not mean const is broken.
I already saw something similar. Basically you invoke a cost function that invoke something else that modifies the object without knowing it.
Consider this as well:
This is not C++11, but does the same: The point is that const is not transitive.
callinc()
doesn't change itselfa
andincval
doesn't changeb
. Note that inmain
you can even declareconst A a(b);
instead ofA a(b);
and everything compile the same.This works from decades, and in your sample you're just doing the same: simply you replaced class B with a lambda.
EDIT
Changed the main() to reflect the comment.
You code is correct, because you don't use the const reference to modify the object. The lambda function uses completely different reference, which just happen to be pointing to the same object.
In the general, such cases does not subvert the type system, because the type system in C++ does not formally guarantee, that you can't modify the const object or the const reference. However modification of the const object is the undefined behaviour.
From [7.1.6.1] The cv-qualifiers:
The oops() method isn't allowed to change the constness of the object. Furthermore it doesn't do it. Its your anonymous function that does it. This anonymous function isn't in the context of the object, but in the context of the main() method which is allowed to modify the object.
Your anonymous function doesn't change the this pointer of oops() (which is defined as const and therefore can't be changed) and also in no way derives some non-const variable from this this-pointer. Itself doesn't have any this-pointer. It just ignores the this-pointer and changes the bc variable of the main context (which is kind of passed as parameter to your closure). This variable is not const and therefore can be changed. You could also pass any anonymous function changing a completely unrelated object. This function doesn't know, that its changing the object that stores it.
If you would declare it as
then the main function also would handle it as const object and couldn't change it.
Edit: In other words: The const attribute is bound to the concrete l-value (reference) accessing the object. It's not bound to the object itself.