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

时间:2011-08-03 10:04:20

标签: c++ smart-pointers virtual-functions return-type

在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

,我的第二个例子确实无法编译

4 个答案:

答案 0 :(得分:45)

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

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

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的指针或从其派生的类。

答案 1 :(得分:2)

  

在此示例中,Derived::Clone隐藏Base::Clone而不是覆盖它

,它不会隐藏它。实际上,它是compilation error

您不能使用另一个仅在返回类型上有所不同的函数来覆盖或隐藏虚函数;所以返回类型应该相同或covariant,否则程序是非法的,因此错误。

因此,这意味着我们无法通过其他方式将shared_ptr<D>转换为shared_ptr<B>。唯一的方法是B*D*关系(您已经在问题中排除了这种关系)。

答案 2 :(得分:2)

@ymett使用answer技术改进了CRTP。这样您就不必担心忘记在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 { ... }
};

答案 3 :(得分:0)

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

这导致了为什么你想这样做的问题。另一种方法可能是强制(外部)功能,您可以在其中收到shared_ptr<Base>,如果可能,可以将其强制转换为shared_ptr<Derived>。也许有些东西:

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