使用智能指针返回类型协方差

Return Type Covariance with Smart Pointers

本文关键字:方差 返回类型 指针 智能      更新时间:2023-10-16

在c++中我们可以这样做:

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

但是,下面的代码不会达到同样的效果:

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

在这个例子中,Derived::Clone 隐藏了 Base::Clone而不是重写,因为标准规定重写成员的返回类型只能从引用(或指针)到基到引用(或指针)到派生。有什么聪明的解决方法吗?当然,有人可能会争辩说,Clone函数应该返回一个普通指针,但现在让我们忘记它-这只是一个说明性的例子。我正在寻找一种方法,可以将虚拟函数的返回类型从智能指针更改为Base到智能指针Derived

提前感谢!

更新:我的第二个例子确实不能编译,由于Iammilind

您不能直接这样做,但是有几种方法可以在非虚拟接口习惯用法的帮助下模拟它。

在原始指针上使用协方差,然后包装它们

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()); }
};

这只适用于当你有一个原始指针作为起始点时。

通过转换

模拟协方差
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()); }
};

这里你必须确保所有Derived::doClone的重写确实返回指向Derived或从它派生的类的指针。

在这个例子中,Derived::Clone隐藏了Base::Clone而不是覆盖它

,它不会隐藏它。实际上,这是一个编译错误。

不能用另一个只在返回类型上不同的函数覆盖或隐藏虚函数;因此,返回类型应该是相同的或协变的,否则程序是非法的,因此会出现错误。

因此,这意味着没有其他方法可以将shared_ptr<D>转换为shared_ptr<B>。唯一的方法是拥有B*D*关系(您已经在问题中排除了)。

@ymett使用CRTP技术对一个很棒的答案进行了改进。这样,您就不必担心忘记在派生类中添加非虚函数。

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 { ... }
};

我想到了一些想法。首先,如果您可以执行第一个版本,只需将Clone隐藏,编写另一个实际返回派生指针的受保护_clone。两个Clone都可以使用它。

这就引出了你为什么要这样做的问题。另一种方法可能是强制(外部)函数,在该函数中,您可以接收shared_ptr<Base>,并在可能的情况下将其强制为shared_ptr<Derived>。也许可以这样写:

template <typename B, typename D>
shared_ptr<D> coerce(shared_ptr<B>& sb) throw (cannot_coerce)
{
// ...
}