我有一个“接口”作为.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' - &gt; Implementor :: parent'从'std :: shared_ptr'转换为'std :: shared_ptr'
所以,基本上std :: shared_ptr是一个包装器,编译器不知道如何从wrapper<apple>
传递到wrapper<fruit>
,即使apple扩展了果实。
我如何规避这种行为?
编辑:在c ++中看起来仍然无法实现这一点,因为协变类型只适用于指针/引用,而不是像std :: shared_ptr这样的包装器...耻辱:(
答案 0 :(得分:5)
你做不到。协变返回类型仅适用于原始指针和引用,因为编译器知道它们是如何工作的。为了使其适用于任意类型,编译器需要能够被告知“使用协变返回类型是安全的”,就像C#与out T
泛型参数一样,但C ++中没有这样的特性。 / p>
不幸的是,您需要在std::shared_ptr<Parent>
中返回Implementor
。
答案 1 :(得分:2)
您可以同时满足虚拟接口,并通过两个成员函数为有权访问派生类的人提供其他信息:
struct Implementor : ISomeInterface
{
shared_ptr<Parent> getX() override { return getX_fromImplementor(); }
shared_ptr<Child> getX_fromImplementor() // not virtual!
{
// real implementation here
}
};
答案 2 :(得分: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()
,并公开公共{{1调度到正确的重载。
你可以用CRTP略微简化这一点,但是......它会更难理解。
getX() { return getX_impl(this); }
现在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)
无意义地推到堆栈上的字节数减少,并保留未初始化的字节。如果您在配置的构建中发现它,请告诉我。 ;)