0

I want to implement a function which returns a reference to Base which actually comprises Derived (types are polymorphic). Something among the lines of the following (incorrect) code:

struct Base { virtual ~Base() {} };
struct Derived: Base { int x = 5;};

const Base& get() {
    Derived d = {};
    const Base& b = d;
    return b;
}

int main() {
    const Base& b = get();
    const auto* a = dynamic_cast<const A*>(&b);
}

OBVIOUSLY, this code causes undefined behaviour as b in main is a dangling reference.

So, I reckon the main obstacle to be the following:

  • To ~pass Derived as Base~, we need to create a reference. No reference to a local variable can be returned from a function as it would be referring to stack-deallocated memory.

Are there any workarounds for this problem (like using pointers instead references or something)?

Zhiltsoff Igor
  • 1,812
  • 8
  • 24
  • It's not clear (to me) what you need to do. If you need to create an instance, I'd check how factory functions are implemented. If you just want a base reference to an existing instance you could define a `const Base& get() const noexcept { return *this; }` in base class, although is not really necessary because as you know you can just write `const Base& b = d;` – MatG Dec 19 '21 at 11:07
  • @MatG I want to return `Derived` from a function as if it were `Base`. The only way I know uses references and pointers, which get invalidated when we leave the function's scope. I wonder how this could be achieved – Zhiltsoff Igor Dec 19 '21 at 11:16
  • 1
    So it's a factory function. I usually instantiate the derived class on the heap (new) in a `std::unique_ptr` returned from the function, since you need an owner of the allocated memory. – MatG Dec 19 '21 at 12:01
  • @MatG I am sorry, I am a newbie and do not know what a factory is :(. Can you provide a brief overview in answer?? – Zhiltsoff Igor Dec 19 '21 at 12:02

1 Answers1

0

If you have to use dynamic_cast I would recommend using pointers instead of references. And to make the API a bit clearer I would use unique_ptr or shared_ptr whenever you're dealing with owning pointers and only use raw pointers when you give temporary access to the objects, like when passing them as parameters to functions (that shouldn't claim ownership). This would aid in communicating whenever ownership is transfered (unique_ptr) or expected to be (possible) shared (shared_ptr).

Another thing to keep in mind is that not all builds enabled RTTI (runtime type information) which is required for dynamic_cast. Objects can usually be designed in such a way that you never have to retrieve the exact type of object.

However, to make your code compile I have three alternative versions.

With references

struct Base { virtual ~Base() {} };
struct Derived : Base { int x = 5; };

Derived d = {};

const Base& get() {
    return d;
}

int main() {
    const Base& b = get();
    const Derived& a = *dynamic_cast<const Derived*>(&b); // This would be undefined behaviour if b wasn't of type Derived as dynamic_cast would return null
}

With unique_ptr

#include <memory>

struct Base { virtual ~Base() {} };
struct Derived : Base { int x = 5; };

std::unique_ptr<const Base> get() {
    return std::make_unique<const Derived>();
}

int main() {
    std::unique_ptr<const Base> b = get();
    const Derived* d = dynamic_cast<const Derived*>(b.get());
}

With shared_ptr

#include <memory>

struct Base { virtual ~Base() {} };
struct Derived : Base { int x = 5; };

std::shared_ptr<const Base> get() {
    return std::shared_ptr<const Derived>();
}

int main() {
    std::shared_ptr<const Base> b = get();
    std::shared_ptr<const Derived> d = std::dynamic_pointer_cast<const Derived>(b);
}
Sven Almgren
  • 183
  • 10
  • It may also be worth remarking that unique_ptr is usually the more general choice, as it is easy for the caller to turn that into a shared_ptr, but not easy to go the other way. – Jeff Garrett Dec 20 '21 at 14:25