Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/130.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如何使用协变参数重写函数(在抽象基类中)_C++_Abstract Class_Covariance - Fatal编程技术网

C++ 如何使用协变参数重写函数(在抽象基类中)

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

我有几个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的问题。事实上,如果你用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++的老时尚,但是,我可以给你任何建议/来源,我将非常感激。