Search code examples
c++referencepolymorphismpass-by-reference

Returning reference to polymorphic object


I've read that references to object retain polymorphic behaviors, with that in mind I've set out to build an event framework based on const references passed in function calls and various callbacks, it works great until I needed to get an event as a return from function. Please consider following code:

#include <iostream>
#include <memory>

class Base
{
public:
  inline Base(int type) : m_type(type){}
  virtual ~Base(){};
  virtual int method() const = 0;
  int m_type;
};

class Derived1 : public Base
{
public:
  inline Derived1(const int data) 
    : Base(1)
    , m_data(data){}
  virtual inline int method() const override {return m_data;} 

  int m_data;
};

// class Derived2 ...
// class Derived3 ...
// class Derived4 ...

const Base* ptr_factory()
{
  const Derived1* product = new Derived1(5);
  return product;
}

const Base& ref_factory()
{
  const Derived1 product(5);
  return std::move(product);
}

void do_the_thing(const Base& data)
{
  // if m_type is 1
  const Derived1& data_cast = dynamic_cast<const Derived1&>(data);
  
  std::cout<<"Hello World " << data.method();
}

int main()
{
  // pointer
  std::unique_ptr<const Base> ptr;
  ptr.reset(ptr_factory());
  do_the_thing(*ptr);
  return 0;
 
 // reference   
 // cannot create Base object here, because abstract, and cannot create Derived because I do not yet know which one
 const Base& ref = ref_factory(); <- undefined behavior and lots of sadness
 do_the_thing(ref);
 
 return 0;
}

The reference based ref_factory produces undefined behavior, I know that, std::move is not moving anything in reality just giving me a reference (and false hope) to already destroyed local object. My question is: Is there a way to avoid pointers in given scenario ? I cannot create even an empty, default constructed, object in main because base class is abstract (and even if it weren't the derived data would be probably sliced later on). I also cannot create Derived object in main, because the factory itself only knows what actual type it will eventually be. Before you ask what beef I have with pointers - I don't, but just look how clean the code looks on the reference based parts. The ideal solution would be to have the ref_factory create the object and move the object's ownership to main. Is this possible within C++14 or am I stuck with having to use pointers and new in this part of the framework ?


Solution

  • My question is: Is there a way to avoid pointers in given scenario?

    Sure, you can return a value type object which internally contains a pointer (or reference) to the dynamic object. If it isn't going to do anything except manage the lifetime of the contained dynamic object, there's no practical point in making this anything other than unique_ptr<Base> though.

    The ideal solution would be to have the ref_factory create the object and move the object's ownership to main.

    The core guidelines broadly prefer using unique_ptr for representing (and transferring) ownership (R.20-21), and also suggest that neither raw pointers nor references should be owning (R3 and R4).

    You could absolutely write a new RAII value object which contains an owning reference to your dynamic type, but it'd be extra work to make something less standard and more surprising.

    Is this possible within C++14 or am I stuck with having to use pointers and new

    You shouldn't be using raw pointers and new expressions anyway. Use std::unique_ptr<Base> and std::make_unique<Base>(...) instead. The unique pointer itself can be moved just fine, and you can give it a typedef or using type alias if you don't even want to see the ptr in unique_ptr.