如何使用协变参数覆盖函数(在抽象基类中)

时间:2016-03-21 17:23:31

标签: 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
{
    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的问题。实际上,如果你用条形注释掉三条线,那么一切都很好。

我认为对这种现象的技术解释是C ++不支持参数的协方差(但它确实支持参数的逆变)。

帖子C++ covariance in parameters的答案表明我无法为我的班级D定义一个接口(即一个抽象类)。

是否有一些简单或传统的方法来创建单个"界面"我的所有课程D?或者,可能存在用于隐藏这些类的不同实现的不同设计模式。

提前感谢您的意见和建议。

3 个答案:

答案 0 :(得分:0)

你不能,而且有充分的理由。派生类不能添加比其派生的接口更具限制性的前置条件,而不会破坏存在的OOP的每个原则。通过要求参数在接口实现中更具体,这正是您正在做的事情。

可以说这样的事情可能有用:

struct IfaceA {};
struct IfaceB : IfaceA {};

struct Base { void f(IfaceB &); };
struct Derived : Base { void f(IfaceA &); };

这减少了先决条件而不是增加它们,所以没关系。它完全没有用C ++或我知道的任何其他语言来完成,所以你不能这样做。

在这两种情况下,您都可以使用替代参数类型进行重载并调用重写版本。

与返回类型相反的情况。返回值是后置条件。您可以使帖子条件更具体,但不能使它们更广泛。因此,您可以返回派生类型,但不能通过返回更抽象的类型来扩展它。 C ++实现协变返回,但至少有一个非常常用的编译器非常糟糕,因此存在大量错误。

答案 1 :(得分:0)

根据您提供的代码,您尝试覆盖两个不同的功能签名。 您拥有的最佳选择是使用相同的签名,然后转换结果。 一个简单的例子,

// 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());
}

我希望这可以解决您的问题,或者至少为您提供线索。

答案 2 :(得分:0)

你能使用模板化的基类吗?

template <typename Deriving>
struct Base {
    virtual Deriving& foo() = 0;
    virtual void bar(Deriving&) = 0;
};

struct Deriving : public Base<Deriving> {
    ...
};

您没有单一的接口类,但只有一个接口模板,有时候它足够通用。