【问题标题】:C++: How can I avoid "invalid covariant return type" in inherited classes without casting?C++:如何在不强制转换的情况下避免继承类中的“无效协变返回类型”?
【发布时间】:2010-03-09 16:16:50
【问题描述】:

我有一个非常复杂的类层次结构,其中的类是相互依赖的交叉类:有两个抽象类 A 和 C,其中包含一个分别返回 C 和 A 实例的方法。在他们继承的类中,我想使用协变类型,在这种情况下这是一个问题,因为我不知道一种方法来前向声明继承关系。

我得到一个“test.cpp:22: error: invalid covariant return type for 'virtual D* B::outC()'”-错误,因为编译器不知道 D 是 C 的子类。

class C;

class A {
public:
        virtual C* outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class D;

class B : public A {
public:
        D* outC();
};

class D : public C {
public:
        B* outA();
};

D* B::outC() {
        return new D();
}

B* D::outA() {
        return new B();
}

如果我将 B::outC() 的返回类型更改为 C*,则示例编译。有没有办法在继承的类中保留 B* 和 D* 作为返回类型(对我来说有办法很直观)?

【问题讨论】:

  • 你真的需要那种类型的纠缠吗? (称它为耦合可能有点短)
  • 有时不是语言的问题,而是我们尝试使用它的方式的问题。如果您的两个层次结构如此紧密地耦合,我认为您最好使用一个层次结构(将ACBD 融合在一起),因为看起来他们无论如何都无法单独工作.
  • 好吧,我有两种类型的类:任务规范和实际执行任务(启动多个线程)的执行程序。规范本身应该是任务的工厂,每个执行者都需要访问它的规范。他们的任务有各种规格。所以执行器包装了一个规范,所以耦合只在一个方向上是强的,在另一个方向上它只是工厂方法。

标签: c++ inheritance covariance


【解决方案1】:

我不知道在 C++ 中有直接耦合的协变成员。您必须要么添加一个层,要么自己实现协变返回。

对于第一个选项

class C;

class A {
public:
        virtual C* outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class BI : public A {
public:
};

class D : public C {
public:
        BI* outA();
};

class B: public BI {
public:
        D* outC();
};

D* B::outC() {
        return new D();
}

BI* D::outA() {
        return new B();
}

第二次

class C;

class A {
public:
        C* outC() { return do_outC(); }
        virtual C* do_outC() = 0;
};

class C {
public:
        virtual A* outA() = 0;
};


class D;

class B : public A {
public:
        D* outC();
        virtual C* do_outC();
};

class D : public C {
public:
        B* outA();
};

D* B::outC() {
        return static_cast<D*>(do_outC());
}

C* B::do_outC() {
        return new D();
}

B* D::outA() {
        return new B();
}

请注意,第二个选项是由编译器隐式完成的(通过一些静态检查来确定 static_cast 是否有效)。

【讨论】:

    【解决方案2】:

    据我所知,如果不进行显式强制转换,就无法做到这一点。问题是类B的定义不能知道DC的子类,直到它看到类D的完整定义,但类D的定义不能知道BA 的子类,直到它看到类 B 的完整定义,所以你有一个循环依赖。这无法通过前向声明解决,因为不幸的是前向声明无法指定继承关系。

    尝试使用模板实现协变 clone() 方法时存在类似的问题,which I found can be solved,但类似的解决方案在这里仍然失败,因为循环引用仍然无法解决。

    【讨论】:

    • 这确实是循环依赖的问题。当您尝试使用交叉依赖的 typedef、枚举、字段等时也是如此。+1
    • 每个人都被这个clone 问题所困扰......我不喜欢这个解决方案的地方在于它的包装部分很难,typedef 不能被向前声明,如果你的客户必须知道不要使用未修饰的名称:/
    【解决方案3】:

    由于客户端的期望,您不能这样做。使用 C 实例时,您无法分辨它是哪种 C(D 或其他)。因此,如果您将 B 指针(来自对派生类的调用,但您在编译时不知道)存储到 A 指针中,我不确定所有内存内容是否正确。

    当您在多态类型上调用方法时,运行时环境必须检查对象的动态类型并移动指针以适应您的类层次结构。我不确定你应该依赖协方差。看看this

    【讨论】:

    • 你误会了。协变返回类型是允许的。如果客户端需要一个A 指针,但你给它一个B 指针,那就可以找到,因为B 的所有实例都是 A 的实例。这种情况下的问题是编译器无法在B::outC 的声明中验证新的返回类型D 是原始返回类型C 的后代。如果编译器可以看到D 的完整定义,它就会允许新签名。请注意,编译器不会对D::outA 发出任何投诉,因为它知道CA 的后代。
    • 在我的示例中,D 类中的协变返回类型不是问题。
    猜你喜欢
    • 1970-01-01
    • 2018-01-04
    • 2016-04-27
    • 2020-12-29
    • 2021-01-29
    • 1970-01-01
    相关资源
    最近更新 更多