C++ 如何使用协变参数重写函数(在抽象基类中)
我有几个D类,其中有表格的公共部分:C++ 如何使用协变参数重写函数(在抽象基类中),c++,abstract-class,covariance,C++,Abstract Class,Covariance,我有几个D类,其中有表格的公共部分: class D { public: D& foo(); void bar(D&); } 我想创建一个抽象类,它们都从中派生 我(天真的)尝试是: // in .h file class B { public: virtual B& foo() = 0; virtual void bar(B&) = 0; } class D : public B
class D
{
public:
D& foo();
void bar(D&);
}
我想创建一个抽象类,它们都从中派生
我(天真的)尝试是:
// in .h file
class B
{
public:
virtual B& foo() = 0;
virtual void bar(B&) = 0;
}
class D : public B
{
public:
D& foo() override;
void bar(D&) override;
}
// in .cpp file
D& D::bar() {return *(new D());}
void D::foo(D& d) {}
编译失败的原因(我最终意识到)相当合理:任何函数都会重写该函数
void bar(B&)=0;
必须为引用类型B的任何参数定义。提供的候选项
virtual void bar(D&) override;
仅为引用类型D的参数(较小的集合)定义
请注意,这不是函数foo的问题。事实上,如果你用bar注释掉这三行,一切都会很好
<> P>我认为,这种现象的技术解释是C++不支持参数的协方差(但它支持参数的逆变)。p>
这篇文章的答案表明我不能为我的类D定义接口(即抽象类)
是否有一些简单或常规的方法来为我所有的类D创建一个“接口”?或者,可能有不同的设计模式来隐藏这些类的不同实现
提前感谢您的意见和建议
丹,你不能,这是有充分理由的。派生类不能添加比其派生接口更严格的前提条件,而不破坏现有OOP的每一条原则。通过在接口的实现中要求参数更加具体,这正是您正在做的 可以提出这样一个论点,即类似这样的东西可能有用:
struct IfaceA {};
struct IfaceB : IfaceA {};
struct Base { void f(IfaceB &); };
struct Derived : Base { void f(IfaceA &); };
这减少了前提条件,而不是增加了前提条件,所以没关系。这不是简单的C++语言,或者我知道的任何其他语言,所以你就是不能这样做。
在这两种情况下,都可以使用替代参数类型进行重载,并调用重写版本
返回类型的情况正好相反。返回值是post条件。您可以使post条件更具体,但不能使其更广泛。因此,您可以返回派生类型,但不能通过返回更抽象的类型来扩展它。C++实现了协变返回,尽管至少有一个,非常常用的编译器做得非常糟糕,以致有很多错误。< P>根据您提供的代码,您试图重写两个不同的函数签名。 最好的选择是使用相同的签名,然后强制转换结果。 一个简单的例子
// in .h file
class B
{
public:
virtual B* foo() = 0;
virtual void bar(B*) = 0;
};
class D : public B
{
public:
B* foo() override;
void bar(B*) override;
};
// in .cpp file
B* D::foo()
{
D* p=new D();
return p;
}
void D::bar(B* d)
{
D* casted=dynamic_cast<D*>(d);
}
int main(void)
{
D son;
B* father=dynamic_cast<B*>(son.foo());
}
//在.h文件中
B类
{
公众:
虚拟B*foo()=0;
虚空条(B*)=0;
};
D类:公共B类
{
公众:
B*foo()覆盖;
无效条(B*)覆盖;
};
//在.cpp文件中
B*D::foo()
{
D*p=新的D();
返回p;
}
void D::bar(B*D)
{
D*casted=动态_cast(D);
}
内部主(空)
{
D儿子;
B*father=dynamic_cast(son.foo());
}
我希望这可以解决您的问题,或者至少给您一个线索。您可以使用模板基类吗
template <typename Deriving>
struct Base {
virtual Deriving& foo() = 0;
virtual void bar(Deriving&) = 0;
};
struct Deriving : public Base<Deriving> {
...
};
模板
结构基{
虚拟派生&foo()=0;
虚拟空心条(导出&)=0;
};
结构派生:公共基{
...
};
您没有一个单一的接口类,但是有一个单一的接口模板,它有时是多功能的。没有什么比“协变参数”更好的了。返回类型可能是协变的。您必须在派生类中使用基类中声明的确切签名实现纯虚成员函数。@πάνταῥεῖ 允许协变返回类型。您的意思是
D&D::foo(){return*(new D());}
和void D::bar(D&D){}
?协变参数会在类型系统中创建一个洞,因此是不允许的。你链接的答案没有提到接口,它说你不能有协变参数。这似乎是我想要的。我提出的这个问题看起来很自然。此解决方案是解决此问题的标准/通用方法吗?我试图谨慎地保持良好的编码实践,避免“黑客行为”。这被称为“奇怪的重复模板模式”。这是一个相当普遍的模式,不应该引起任何关注。这似乎是可行的,但我总是对使用动态_类型转换的智慧有点怀疑。我想有些情况下,演员阵容是合适的,但我通常会尽量避免。你确定这是解决方案符合最近发布的C++指南吗?我也反对DyrimCype,检查虚拟表的成本太高,你知道你在做什么,你应该总是使用STATICE-CAST代替。关于C++指导原则,我不得不承认我已经学习过C/C++的老时尚,但是,我可以给你任何建议/来源,我将非常感激。