There's a couple of base classes outside my control like this:-
class BaseNode // Just a POD class. No VTable
{
void foo();
}
class BaseHost
{
public:
BaseNode *getNode()
{
...
}
}
I want to "extend" the functionality of BaseNode::foo
, but the class is effectively sealed.
Here's the suggestion:-
class MyNode: public BaseNode
{
void foo()
{
// do my stuff..., then
BaseNode::foo();
}
}
class MyHost: public BaseHost
{
public:
MyNode *getNode()
{
return (MyNode*) BaseHost::getNode(); // Slicing or UB risk?
}
}
Things will go badly wrong if MyNode
introduces additional class members, or virtual methods - but if those constraints are met, do I still have UB?
Any other gotchas, or do I rethink the design entirely.?
Such a downcasting shall be used with extreme care: it can very easily lead to UB.
The standard guarantees that you can safely convert from MyNode*
to BaseNode*
:
4.10/3: A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class of D. If B is an inaccessible or ambiguous base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.
But let's play with the fire: the standard also let you can cast from BaseNode*
to MyNode*
under certain conditions :
5.2.9/11: A prvalue of type “pointer to cv1 B,” where B is a class type, can be converted to a prvalue of type “pointer to cv2 D,” where D is a class derived from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists, cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D. The null pointer value is converted to the null pointer value of the destination type. If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined
I try to translating these quotes into plain text:
BaseHost::getNode()
returns an upcasted pointer to a MyNode
object, and you have no virtual inheritance in your class hierarchy, then it's ok. BaseHost::getNode()
would return something else (e.g. a pointer to a plain BaseNode
or to another sibling derivate of BaseNode
) you'll have UB. As said, it's dangerous: the UB might already happen during the pointer conversion, before you even attempt to dereference the pointer. So better try to avoid it. If you'd have a polymorphic BaseNode
class (e.g. with a virtual destructor), you could use a safer dynamic_cast
.