Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return Type Covariance with Smart Pointers

In C++ we can do this:

struct Base {    virtual Base* Clone() const { ... }    virtual ~Base(){} };  struct Derived : Base {    virtual Derived* Clone() const {...} //overrides Base::Clone }; 

However, the following won't do the same trick:

struct Base {    virtual shared_ptr<Base> Clone() const { ... }    virtual ~Base(){} };  struct Derived : Base {    virtual shared_ptr<Derived> Clone() const {...} //hides Base::Clone }; 

In this example Derived::Clone hides Base::Clone rather than overrides it, because the standard says that the return type of an overriding member may change only from reference(or pointer) to base to reference (or pointer) to derived. Is there any clever workaround for this? Of course one could argue that the Clone function should return a plain pointer anyway, but let's forget it for now - this is just an illustratory example. I am looking for a way to enable changing the return type of a virtual function from a smart pointer to Base to a smart pointer to Derived.

Thanks in advance!

Update: My second example indeed doesn't compile, thanks to Iammilind

like image 479
Armen Tsirunyan Avatar asked Aug 03 '11 10:08

Armen Tsirunyan


People also ask

Is covariant return types available in C ++ 17?

C++'s classical OOP system supports “covariant return types,” but it does not support “contravariant parameter types.”

Are smart pointers An example of Raii?

RAII stands for Resource Acquisition Is Initialization. The idea behind this idiom: for any resource acquired, an object should be initialized that will own that resource and close it in the destructor. Smart pointers are a prominent example of RAII . They help avoid memory leaks.

Are smart pointers on the stack or heap?

As shown in the example, a smart pointer is a class template that you declare on the stack, and initialize by using a raw pointer that points to a heap-allocated object.

Are shared pointers smart pointers?

Shared pointers are smart pointers that keep a count of how many instances of the pointer exist, and only clean up the memory when the count reaches zero. In general, only use shared pointers (but be sure to use the correct kind--there is a different one for arrays).


2 Answers

You can't do it directly, but there are a couple of ways to simulate it, with the help of the Non-Virtual Interface idiom.

Use covariance on raw pointers, and then wrap them

struct Base { private:    virtual Base* doClone() const { ... }  public:    shared_ptr<Base> Clone() const { return shared_ptr<Base>(doClone()); }     virtual ~Base(){} };  struct Derived : Base { private:    virtual Derived* doClone() const { ... }  public:    shared_ptr<Derived> Clone() const { return shared_ptr<Derived>(doClone()); } }; 

This only works if you actually have a raw pointer to start off with.

Simulate covariance by casting

struct Base { private:    virtual shared_ptr<Base> doClone() const { ... }  public:    shared_ptr<Base> Clone() const { return doClone(); }     virtual ~Base(){} };  struct Derived : Base { private:    virtual shared_ptr<Base> doClone() const { ... }  public:    shared_ptr<Derived> Clone() const       { return static_pointer_cast<Derived>(doClone()); } }; 

Here you must make sure that all overrides of Derived::doClone do actually return pointers to Derived or a class derived from it.

like image 198
ymett Avatar answered Oct 09 '22 02:10

ymett


In this example Derived::Clone hides Base::Clone rather than overrides it

No, it doesn't hide it. Actually, it's a compilation error.

You cannot override nor hide a virtual function with another function that differs only on return type; so the return type should be the same or covariant, otherwise the program is illegal and hence the error.

So this implies that there is no other way by which we can convert shared_ptr<D> into shared_ptr<B>. The only way is to have B* and D* relationship (which you already ruled out in the question).

like image 36
iammilind Avatar answered Oct 09 '22 01:10

iammilind