将std::shared_ptr<Parent>转换为std::shared_ptr<Child>

3

我有一个.h文件中的“接口”,其中有一个如下的虚方法:

class ISomeInterface {
    public:
        virtual std::shared_ptr<Parent> getX() = 0;
}

现在父类是“抽象的”,在接口的实现者中我使用一个实际的类。因此,我想要这样做:

class Implementor : public ISomeInterface {
    public:
        std::shared_ptr<Child> getX() = { return this->x; }
}

但是我得到了:

无法将‘((Implementor*)this)->Implementor::parent’从 ‘std::shared_ptr’ 转换为 ‘std::shared_ptr’

所以,基本上,std::shared_ptr是一个包装器,编译器不知道如何从wrapper<apple>转换到wrapper<fruit>,尽管apple扩展了fruit。

我该如何规避这种行为?

编辑: 看起来在C++中仍然不可能实现这个,因为协变类型仅适用于指针/引用,而不适用于像std::shared_ptr这样的包装器...太可惜了 :(

3个回答

4
您无法这样做。协变返回类型只适用于原始指针和引用,因为编译器知道它们如何工作。为了使任意类型都能够使用这个特性,编译器需要被告知“这是安全的,可以与协变返回类型一起使用”,就像C#中使用out T通用参数那样,但在C++中没有这样的特性。
不幸的是,在Implementor中,您需要返回std::shared_ptr。

那么,我该如何将std::shared_ptr<Child>转换为std::shared_ptr<Parent>? - user4063815
一个“普通转换”足够吗?return (std::shared_ptr<Parent>)this->x; - user4063815
1
@Sorona,你不能声明返回类型为std::shared_ptr<Child>,但是你可以在返回类型为std::shared_ptr<Parent>的函数中返回类型为std::shared_ptr<Child>的值,因为它可以转换为std::shared_ptr<Parent>return this->x;将起作用。 - milleniumbug
不在.hpp文件中,编译器不允许这样做。在实现中,它可以不用转换就能工作...对此我很抱歉!编辑:算了吧,它只是不允许这样做,因为我使用了前向声明而不是#include "child.hpp" - user4063815

1

你可以通过拥有两个成员函数来同时满足虚接口和为那些访问派生类的人提供额外信息:

struct Implementor : ISomeInterface
{
    shared_ptr<Parent> getX() override { return getX_fromImplementor(); }

    shared_ptr<Child> getX_fromImplementor()  // not virtual!
    {
        // real implementation here
    }
};

1

这是可能的。

现在,天真地讲,C++中不允许这样做。C++中的协变返回类型不能以这种方式工作。

但这是C++。C++不会停下来放弃一个功能只因为语言不支持它。我们只需自己编写该功能即可。

关键是您实际上并不关心getX是否实际上是虚拟的;您只想要它具有虚拟行为。

我们创建了一系列非虚拟getX,每个都有一系列getX_impl虚函数进行分派。我们使用final和基于指针的分派使其抵抗小错误。

从用户方面来看,它就像协变返回类型一样。在实现方面,您需要编写两个简短的样板。

这种设计的重要部分是它不进行任何可能不安全的转换。一个简单的快捷方式是放弃final和新的getX_impl方法;代价是我们无法保证更进一步的子类是否真的会放置一个shared_ptr<Child>

父类final派发公共接口。公共接口派发一个新的虚函数,返回shared_ptr<Child>。想要改变行为的子类必须重写那个返回shared_ptr<Child>的函数;他们别无选择,编译器强制执行。
class ISomeInterface {
  virtual std::shared_ptr<Parent> getX_impl(ISomeInterface *) = 0;
public:
  std::shared_ptr<Parent> getX() { return getX_impl(this); }
};

class Implementor : public ISomeInterface {
  std::shared_ptr<Child> x = std::make_shared<Child>();

  virtual std::shared_ptr<Parent> getX_impl(ISomeInterface*) final override { return getX(); }
  virtual std::shared_ptr<Child> getX_impl(Implementor*) { return this->x; }
public:
  std::shared_ptr<Child> getX() { return getX_impl(this); }
};

用户只需调用getX()。它的作用几乎完全像一个具有协变共享指针返回类型的虚拟方法。 它甚至可以与成员函数指针一起使用!
您实现的每个具有新类型的类都会完成父级方法,创建一个新的私有虚拟getX_impl方法返回新类型,使新父级调用getX(),并暴露一个公共getX() {return getX_impl(this);},该公共方法分派到正确的重载。 实时示例
您可以使用CRTP轻微简化此过程,但是……这可能更难理解。
template<class D, class Child, class B>
struct getX_CRTP : B {

  std::shared_ptr<Child> getX() { return static_cast<D*>(this)->getX_impl(static_cast<D*>(this)); }
private:
  virtual decltype( std::declval<B&>().getX() ) getX_impl( B* ) final override { return getX(); }
  virtual std::shared_ptr<Child> getX_impl( D* ) = 0;
};

现在,Implementor 的样子如下:
class Implementor : public getX_CRTP<Implementor, Cihld, ISomeInterface>
{
  std::shared_ptr<Child> x = std::make_shared<Child>();
  virtual std::shared_ptr<Child> getX_impl(Implementor*) override { return this->x; }
};

这样可以使每个派生类的代码更短,但实际上并不更清晰。

我们可能还可以用某种标签替换指向自己类型的指针,将在堆栈上推送的字节从sizeof(ptr)减少到1,并且未初始化。如果您在分析构建时发现了,请告诉我。;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接