Search code examples
c++inheritanceundefined-behavior

Object slicing or UB risk?


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.?


Solution

  • 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:

    • If you are sure that BaseHost::getNode() returns an upcasted pointer to a MyNode object, and you have no virtual inheritance in your class hierarchy, then it's ok.
    • But if 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.