这是可能的。
现在,天真地讲,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
,并且未初始化。如果您在分析构建时发现了,请告诉我。;)
return (std::shared_ptr<Parent>)this->x;
- user4063815std::shared_ptr<Child>
,但是你可以在返回类型为std::shared_ptr<Parent>
的函数中返回类型为std::shared_ptr<Child>
的值,因为它可以转换为std::shared_ptr<Parent>
。return this->x;
将起作用。 - milleniumbug.hpp
文件中,编译器不允许这样做。在实现中,它可以不用转换就能工作...对此我很抱歉!编辑:算了吧,它只是不允许这样做,因为我使用了前向声明而不是#include "child.hpp"
。 - user4063815