开发者

const casting an int in a class vs outside a class

I read on the wikipedia page for Null_pointer that Bjarne Stroustrup suggested defining NULL as

const int NULL = 0;

if "you feel you must define NULL." I instantly thought, hey.. wait a minute, what about const_cast?

After some experimenting, I found that

int main() {
    const int MyNull = 0;
    const int*  ToNull = &MyNull;
    int* myptr = const_cast<int*>(ToNull);
    *myptr = 5;
    printf("MyNull is %d\n", MyNull);
    return 0;
}

would print "MyNull is 0", but if I make the const int belong to a class:

class test {
public:
    test() : p(0) { }
    const int p;
};
int main() {
    test t;
    const int* pptr = &(t.p);
    int* myptr = const_cast<int*>(pptr);
    *myptr = 5;
    printf("t.p is %d\n", t.p);
    return 0;
}

then it prints "t.p is 5"!

Why is there a difference be开发者_如何转开发tween the two? Why is "*myptr = 5;" silently failing in my first example, and what action is it performing, if any?


First of all, you're invoking undefined behavior in both cases by trying to modify a constant variable.

In the first case the compiler sees that MyNull is declared as a constant and replaces all references to it within main() with a 0.

In the second case, since p is within a class the compiler is unable to determine that it can just replace all classInstance.p with 0, so you see the result of the modification.


Firstly, what happens in the first case is that the compiler most likely translates your

printf("MyNull is %d\n", MyNull);

into the immediate

printf("MyNull is %d\n", 0);

because it knows that const objects never change in a valid program. Your attempts to change a const object leads to undefined behavior, which is exactly what you observe. So, ignoring the undefined behavior for a second, from the practical point of view it is quite possible that your *myptr = 5 successfully modified your Null. It is just that your program doesn't really care what you have in your Null now. It knows that Null is zero and will always be zero and acts accordingly.

Secondly, in order to define NULL per recommendation you were referring to, you have to define it specifically as an Integral Constant Expression (ICE). Your first variant is indeed an ICE. You second variant is not. Class member access is not allowed in ICE, meaning that your second variant is significantly different from the first. The second variant does not produce a viable definition for NULL, and you will not be able to initialize pointers with your test::p even though it is declared as const int and set to zero

SomeType *ptr1 = Null; // OK

test t;
SomeType *ptr2 = t.p; // ERROR: cannot use an `int` value to initialize a pointer    

As for the different output in the second case... undefined behavior is undefined behavior. It is unpredictable. From the practical point of view, your second context is more complicated, so the compiler was unable to prefrom the above optimization. i.e. you are indeed succeeded in breaking through the language-level restrictions and modifying a const-qualified variable. Language specification does not make it easy (or possible) for the compilers to optimize out const members of the class, so at the physical level that p is just another member of the class that resides in memory, in each object of that class. Your hack simply modifies that memory. It doesn't make it legal though. The behavior si still undefined.

This all, of course, is a rather pointless exercise. It looks like it all began from the "what about const_cast" question. So, what about it? const_cast has never been intended to be used for that purpose. You are not allowed to modify const objects. With const_cast, or without const_cast - doesn't matter.


Your code is modifying a variable declared constant so anything can happen. Discussing why a certain thing happens instead of another one is completely pointless unless you are discussing about unportable compiler internals issues... from a C++ point of view that code simply doesn't have any sense.

About const_cast one important thing to understand is that const cast is not for messing about variables declared constant but about references and pointers declared constant.

In C++ a const int * is often understood to be a "pointer to a constant integer" while this description is completely wrong. For the compiler it's instead something quite different: a "pointer that cannot be used for writing to an integer object".

This may apparently seem a minor difference but indeed is a huge one because

  1. The "constness" is a property of the pointer, not of the pointed-to object.

  2. Nothing is said about the fact that the pointed to object is constant or not.

  3. The word "constant" has nothing to do with the meaning (this is why I think that using const it was a bad naming choice). const int * is not talking about constness of anything but only about "read only" or "read/write".

const_cast allows you to convert between pointers and references that can be used for writing and pointer or references that cannot because they are "read only". The pointed to object is never part of this process and the standard simply says that it's legal to take a const pointer and using it for writing after "casting away" const-ness but only if the pointed to object has not been declared constant.

Constness of a pointer and a reference never affects the machine code that will be generated by a compiler (another common misconception is that a compiler can produce better code if const references and pointers are used, but this is total bogus... for the optimizer a const reference and a const pointer are just a reference and a pointer).

Constness of pointers and references has been introduced to help programmers, not optmizers (btw I think that this alleged help for programmers is also quite questionable, but that's another story).

const_cast is a weapon that helps programmers fighting with broken const-ness declarations of pointers and references (e.g. in libraries) and with the broken very concept of constness of references and pointers (before mutable for example casting away constness was the only reasonable solution in many real life programs).

Misunderstanding of what is a const reference is also at the base of a very common C++ antipattern (used even in the standard library) that says that passing a const reference is a smart way to pass a value. See this answer for more details.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜