Search code examples
c++castingstructurederived

C++ casting corrupted structure


I have 2 structures:

typedef struct foo
{} foo_t;

typedef struct bar : foo_t
{
    int value;
} bar_t;

Well foo should never be created, it's like an abstract class (I don't create any in my code). I also have a method which returns a bar, but returns it as foo:

foo_t giveMeMyBar()
{
    bar_t ret;
    ret.value = 5;
    return ret;
}

Then I call the method and try to cast it into the derived structure:

foo_t f = giveMeMyBar();
bar_t b = ((bar_t *) (&f));

And the value member of 'b' is completely wrong (a very big negative number). I'm sure that I'm casting wrong (or doing something else wrong) but I came from java, casting is really easy there.


Solution

  • By returning foo_t by value, you only return the foo_t part of the object, with type foo_t - this is sometimes known as slicing.

    Since there is no bar_t object, the cast is invalid, and using the resulting pointer gives undefined behaviour.

    To retain polymorphism, you'll have to return a pointer or reference to an object that still exists after the function returns - that is, not to a local variable. First of all, the base class will need a virtual destructor, so derived classes can be safely managed using a pointer to the base:

    // I'll get rid of the weird C-style typedef for readability
    struct foo {
        virtual ~foo() = default;
    };    
    

    Now you can allocate an object of the derived type, and return a smart pointer to the base type:

    std::unique_ptr<foo> giveMeMyBar() {
        auto ret = std::make_unique<bar>();
        ret->value = 5;
        return ret;
    }
    

    Now you can safely cast the type back:

    auto foo = giveMeMyBar();
    bar * ptr = dynamic_cast<bar*>(foo.get()); // pointer to returned object
    bar & ref = dynamic_cast<bar&>(*foo);      // reference to returned object
    bar copy  = dynamic_cast<bar&>(*foo);      // copy of returned object
    

    If the type was wrong, then the pointer cast will yield a null pointer, while the reference casts will throw an exception.

    Other, less safe casts are available. The C-style cast that you used is the nuclear option, forcing the compiler to accept the conversion in whatever way it can. That often leads to undefined behaviour, so only use it when you're absolutely sure that's what you want.

    I came from java

    Then you will have to unlearn what you have learned. C++ has a very different object model.

    casting is really easy there

    But at a price: all java objects are polymorphic, and are accessed indirectly through managed references. C++ allows you the choice of more efficient, but sometimes less convenient, value types.