Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does casting away constness from "this" and then changing a member value invoke undefined behaviour?

In a response to my comment to some answer in another question somebody suggests that something like

void C::f() const
{
  const_cast<C *>( this )->m_x = 1;
}

invokes undefined behaviour since a const object is modified. Is this true? If it isn't, please quote the C++ standard (please mention which standard you quote from) which permits this.

For what it's worth, I've always used this approach to avoid making a member variable mutable if just one or two methods need to write to it (since using mutable makes it writeable to all methods).

like image 389
Frerich Raabe Avatar asked Jan 04 '13 09:01

Frerich Raabe


People also ask

Is const_cast undefined behavior?

Even though const_cast may remove constness or volatility from any pointer or reference, using the resulting pointer or reference to write to an object that was declared const or to access an object that was declared volatile invokes undefined behavior. So yes, modifying constant variables is undefined behavior.

Do not cast away a const qualification?

Do not cast away a const qualification on an object of pointer type. Casting away the const qualification allows a program to modify the object referred to by the pointer, which may result in undefined behavior.

Why does const_ cast exist?

On the opposite end, the reason for const_cast to exist was adapting to old C code that did not support the const keyword. For some time functions like strlen would take a char* , even though it is known and documented that the function will not modify the object.

Can you cast a const?

const_cast can be used to add const ness behavior too. From cplusplus.com: This type of casting manipulates the constness of an object, either to be set or to be removed.


1 Answers

It is undefined behavior to (attempt to) modify a const object (7.1.6.1/4 in C++11).

So the important question is, what is a const object, and is m_x one? If it is, then you have UB. If it is not, then there's nothing here to indicate that it would be UB -- of course it might be UB for some other reason not indicated here (for example, a data race).

If the function f is called on a const instance of the class C, then m_x is a const object, and hence behavior is undefined (7.1.6.1/5):

const C c;
c.f(); // UB

If the function f is called on a non-const instance of the class C, then m_x is not a const object, and hence behavior is defined as far as we know:

C c;
const C *ptr = &c;
c->f(); // OK

So, if you write this function then you are at the mercy of your user not to create a const instance of C and call the function on it. Perhaps instances of C are created only by some factory, in which case you would be able to prevent that.

If you want a data member to be modifiable even if the complete object is const, then you should mark it mutable. That's what mutable is for, and it gives you defined behavior even if f is called on a const instance of C.

As of C++11, const member functions and operations on mutable data members should be thread-safe. Otherwise you violate guarantees provided by standard library, when your type is used with standard library functions and containers.

So in C++11 you would need to either make m_x an atomic type, or else synchronize the modification some other way, or as a last resort document that even though it is marked const, the function f is not thread-safe. If you don't do any of those things, then again you create an opportunity for a user to write code that they reasonably believe ought to work but that actually has UB.

like image 169
Steve Jessop Avatar answered Sep 18 '22 23:09

Steve Jessop