Is it undefined behavior to cast from base class t

2019-07-29 23:59发布

问题:

I've encountered myself in a problem where casting to the derived class would solve the problem. I've found an answer on S.O that says it can lead to UB, testing it, it both compiled and worked correctly. Is it undefined behavior? If it is what would be a correct approach to this problem?

class A
{
public:
    A(){};
    ~A(){}
};

class B : public A
{
public:
    B(){};
    ~B(){}
    void Show() { std::cout << "Show" << std::endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    A a;
    B* b = static_cast<B*>(&a);
    b->Show();

    return 0;
}

回答1:

As long as the pointer to a base type actually points to an instance of a derived type, then such usage is not undefined according to the C++ standard. However, in your code sample, the pointer b does not point to an instance of B or any of its derived types (which there are none), it points to an instance of A. So your code does in fact invoke undefined behavior.

I've found an answer on S.O that says it can lead to UB, testing it, it both compiled and worked correctly.

The fact that some code compiles and works correctly does not rule out the possibility of code invoking undefined behavior, because undefined behavior includes "appears to work". The reason why you should avoid undefined behavior is because there's no guarantee that it will work the same way the next time you invoke UB.

Is it undefined behavior? If it is what would be a correct approach to this problem?

In your sample, yes, it is undefined behavior. The correct approach would depend on what your code is actually supposed to do, as the example you provide is an academic example at best.

To make it clear, the following modification to your main() function has well-defined behavior and is explictly allowed by the C++ standard:

B objectB;
A* ptrA = &objectB;

B* b = static_cast<B*>(ptrA);
b->Show();

Here, it's well defined because the pointer ptrA actually points to an instance of B, even though the pointer itself has type A*. In the above example, casting from A* to B* then invoking one of B's functions on the casted pointer will work. The difference is that in the example in your question, b does not actually point to an instance of B.


The relevant clause (emphasis mine):

C++ standard 5.2.9/8 Static cast [expr.static.cast]

An rvalue of type “pointer to cv1 B”, where B is a class type, can be converted to an rvalue of type “pointer to cv2 D”, where D is a class derived (clause 10) from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D. The null pointer value (4.10) is converted to the null pointer value of the destination type. If the rvalue of type “pointer to cv1 B” points to a B that is actually a sub-object of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the result of the cast is undefined.



回答2:

You can cast a pointer to a base object that is indeed pointing to a derived instance to a pointer to a derived object.

In your code however the object pointed by &a is not a derived object but a base object and what you are doing is indeed undefined behaviour.

In implementations I know it should "work" if the class has no virtual functions or bases and the derived object doesn't add any data member but just methods. Still it's something that is not formally guaranteed to work.

Just don't do that.



回答3:

The cast is allowed (and hence works) if the pointed to object really is a B. In your example, it is a plain A and therefore, you get undefined behaviour.

Which leaved the question, why does it work? It works (or seems to work), because the method you called is not accessing the object. It will more likely fail if you add some member variables to B and try to access them in Show() or if you make Show a virtual function. But in any case it's UB, so basically anything can happen.