Search code examples
c++castingreferencepolymorphismundefined-behavior

Is it possible to convert Base& to Derived& without object copying or undefined behavior?


Problem: I have a class (PortableFoo) designed to be very portable. It contains a scoped class PortableBar. The surrounding codebase (call it Client A) requires both Foo and Bar to have a function that cannot be implemented portably, and Foo's implementation must call Bar's implementation. The following is a solution that compiles and works in GCC, but I know invokes undefined behavior when it casts the reference from base to derived:

//Code in portable codebase
class PortableFoo
{
public:
    int a = 1;
    class PortableBar
    {
    public:
        int b = 1;
    } bar;
};

//Code in Client A
#include <iostream>
class AdaptedFoo: public PortableFoo
{
public:
    int fancy_foo_func()
    {
        return a + ((AdaptedBar&)bar).fancy_bar_func();
    }
    
    class AdaptedBar: public PortableBar
    {
    public:
        int fancy_bar_func()
        {
            return b;
        }
    };
};

int main()
{
    AdaptedFoo foo;
    std::cout<<foo.fancy_foo_func(); //prints "2"
    return 0;
}

As in the example, it's no issue for me to simply construct objects as AdaptedFoo in the first place in ClientA, the problem is that AdaptedFoo.bar is still of type PortableBar. I know of three solutions to the problem but each have significant drawbacks:

  1. The above solution, with an undefined behavior reference cast that may cause a segmentation fault if sizeof(AdaptedBar) != sizeof(PortableBar). (In the real codebase, I have tested that it always causes a segmentation fault if there's a size mismatch, because I actually store a vector of Bars. The minimal example is not quite complex enough for the segfault to show up.)
  2. Make fancy_bar_func() virtual with an empty implementation in PortableBar. This is only a solution to the minimal example, my real problem has the additional complication that fancy_bar_func() must be templated, and templated functions cannot be virtual. (Specifically, it is the serialize() function used by Boost Serialization - the third party library demands it be a templated function.)
  3. Give AdaptedBar a constructor with the signature AdaptedBar(PortableBar&), allowing temporaries of type AdaptedBar to be constructed from references to PortableBar. The problem is that such a temporary must copy all the members of PortableBar, and in the real world, PortableBar is an extremely large object, so making temporary copies during serialization isn't suitable.

Question: Since solutions 2 and 3 fail compilation and system requirements respectively, the undefined behavior is my only known valid solution right now. Are there other approaches I'm missing that do not invoke UB?


Solution

  • You ran into one of the main reason why public member variables are a bad idea.

    If all access to the class goes through functions, overloading the accessors to return adapters that wrap a reference to the underlying member would be a transparent refactor, and would give you Mostly what you want.

    Like so:

    class PortableFoo
    {
    public:
      class PortableBar
      {
      public:
        int b = 1;
      };
    
      int get_a() { return a;}
      bar& get_bar() { return bar;}
    
      private:
        int a = 1;
        PortableBar bar;
    };
    
    class AdaptedFoo : public PortableFoo
    {
    public:    
        class AdaptedBar
        {
            PortableFoo::PortableBar& bar;
    
        public:
            AdaptedBar(PortableFoo::PortableBar& src_bar) : bar(src_bar) {}
            int fancy_bar_func()
            {
                return bar.b;
            }
        };
    
        int get_a() { return a;}
        AdaptedBar get_bar() {return AdaptedBar{PortableFoo::get_bar()};}
        
        int fancy_foo_func()
        {
            return a + get_bar().fancy_bar_func();
        }
    };