2

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;
}
Vinícius
  • 15,498
  • 3
  • 29
  • 53

3 Answers3

9

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.

In silico
  • 51,091
  • 10
  • 150
  • 143
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.

6502
  • 112,025
  • 15
  • 165
  • 265
2

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.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • In this simple case, there isn't any problem related to the accessing of members in the base class. The members will be in 'the right place', so to speak. `&a.some_member == &(b->some_member)`, and therefore the `Show()` method should work even if it were modified to print the values of any members. (Now, of course, one can talk about UB to dismiss this whole question! But if we're going to try to explain/predict what might happen, I think there are situations where it's hard to practically see how a compiler could decide to do the 'wrong' thing.) – Aaron McDaid Nov 02 '13 at 11:01
  • @AaronMcDaid Even if you assume layout compatible objects it would still just *seem* to work. If you say it "should work", this is wrong as it is still illegal from the standard's point of view. It's undefined behavior and the compiler is allowed to do anything. And if you care to read the title of this question: It is explicitly about undefined behavior. – Daniel Frey Nov 02 '13 at 11:05
  • Hey @Daniel Frey, (I've edited my comment slightly before I saw your response.) I hope your comment now doesn't seem too out-of-place. – Aaron McDaid Nov 02 '13 at 11:08
  • @AaronMcDaid Updated mine as well. – Daniel Frey Nov 02 '13 at 11:09
  • Also, this discussions of UB remind me of [this answer discussion](http://stackoverflow.com/a/8836800/146041) that I took part in. Some of us agreed that UB isn't a 'positive' thing. In other words, if other parts of the language provide a definition of behaviour, then UB can become defined after all. In other words, when the standard says "X is undefined behaviour", it really means "In this section, we have nothing to say about X. It may or may not be defined elsewhere, but this section has no comment to make.". – Aaron McDaid Nov 02 '13 at 11:12
  • I think I can break this down into a more useful question that I'm considering at the moment in a project I'm working on. Is the static_cast itself UB immediately at the point of casting? Or perhaps it's more subtle. Perhaps it simply means the *value* in the cast, stored with `B *b` is undefined? And therefore that we don't actually cross into UB until we attempt to dereference the pointer? – Aaron McDaid Nov 02 '13 at 11:14
  • I've asked my question more fully as [a separate question](http://stackoverflow.com/questions/19741843/if-derived-adds-no-new-members-to-base-and-is-pod-then-is-it-always-safe-to-c) – Aaron McDaid Nov 02 '13 at 12:23