Return Type Covariance with Smart Pointers

非 Y 不嫁゛ 提交于 2019-11-27 03:57:04

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.

iammilind

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).

There is an improvement on a great answer by @ymett using CRTP technique. That way you needn't worry about forgetting to add a non-virtual function in the Derived.

struct Base
{
private:
   virtual Base* doClone() const { ... }

public:
   shared_ptr<Base> Clone() const { return shared_ptr<Base>(doClone()); }

   virtual ~Base(){}
};

template<class T>
struct CRTP_Base : Base
{
public:
   shared_ptr<T> Clone() const { return shared_ptr<T>(doClone()); }
};

struct Derived : public CRTP_Base<Derived>
{
private:
   virtual Derived* doClone() const { ... }
};

Some ideas come to my mind. First, if you can do the first version, just leave that Clone to hide, and write another protected _clone that actually return the derived pointer. Both Clone can make use of it.

That leads to the question on why do you want to that this way. Another way could be a coerce (outside) function, in which you receive a shared_ptr<Base> and can coerce it to a shared_ptr<Derived> if possible. Maybe something along the lines of:

template <typename B, typename D>
shared_ptr<D> coerce(shared_ptr<B>& sb) throw (cannot_coerce)
{
// ...
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!