【问题标题】:How to implement a function with a covariant return type of another derived class?如何实现具有另一个派生类的协变返回类型的函数?
【发布时间】:2016-08-18 19:42:40
【问题描述】:

我的程序有一个抽象类 BaseNode,它有 2 个派生类 ChoiceNode 和 OpponentNode。我想在 BaseNode 中编写一个名为“returnOpposite”的纯虚函数,如果从 ChoiceNode 调用它应该返回一个 OpponentNode,如果从 OpponentNode 调用它应该返回一个 ChoiceNode。

在 BaseNode.h 中

class BaseNode {
    protected:
        virtual BaseNode& returnOpposite() = 0;
}

在 ChoiceNode.h 中

#include "BaseNode.h"

class ChoiceNode: public BaseNode {
    OpponentNode& returnOpposite();
}

在 OpponentNode.h 中

#include "BaseNode.h"

class OpponentNode: public BaseNode {
    ChoiceNode& returnOpposite();
}

我的问题是 Opponent/ChoiceNode 需要了解相反的类,这通常可以通过使用前向声明来解决,但是为了让编译器识别相反的类与 BaseNode 是协变的,它需要有关该类的上下文信息。

据我了解,此信息是通过包含适当的头文件来提供的。但是,这样做会导致某种循环依赖。 ChoiceNode 需要包含 OpponentNode,而 OpponentNode 本身也需要包含 ChoiceNode,但是有了标头保护,OpponentNode 似乎不会知道 ChoiceNode 类声明。

如何解决这个明显的 catch-22?有没有办法在不涉及循环依赖的情况下提供有关类的上下文信息?

【问题讨论】:

    标签: c++ inheritance polymorphism circular-dependency


    【解决方案1】:

    由于您发现的循环依赖问题,您不能使用真正的协变返回类型,正如您所发现的那样。

    但是,您可以使用在引入协变返回类型之前有效的技术。

    首先,我建议将所有类中的声明更改为virtual Node& returnOppositeImpl(),使基类成为纯虚拟的。我还将根据您的类层次结构的其余部分,将函数和所有实现声明为private。然后,您将在每个类returnOpposite 中声明一个非虚拟保护(或公共)方法,并使用上述签名。这些函数定义,因为它们相互影响,不需要协变返回类型,它们的实现可以放在各自的 .cpp 文件中。该实现是一个简单的静态或动态转换,调用returnOppositeImpl 方法。

    使用阴影,您将始终在调用站点调用与静态类型相对应的 returnOpposite,因此它将具有正确的引用类型。但是各个Node类之间的联系的实际知识隐藏在实现中,打破了依赖循环。

    所以,综合起来

    在 BaseNode.h 中

    class BaseNode {
        private:
            virtual Node& returnOppositeImpl() = 0;
        protected: // Or public:
             Node& returnOpposite() {
                return returnOppositeImpl();
             }
    }
    

    在 ChoiceNode.h 中

    #include "BaseNode.h"
    class OpponentNode;
    class ChoiceNode: public BaseNode {
        private: 
            virtual Node& returnOppositeImpl();
        protected: // or public:
            OpponentNode& returnOpposite();
    }
    

    在 ChoiseNode.cpp 中

    #include "ChoiseNode.h"
    #include "OpponentNode.h"
    OpponentNode& ChoiseNode::returnOpposite()
    {
      // You can use static cast here, if Node doesn't have virtual
      // members, and/or if you can guarantee further subclasses
      // will properly always return an OpponentNode reference.
    
      return dynamic_cast<OpponenentNode&>(returnOppositeImpl());
    }
    

    OpponentNode.h 和 OpponentNode.cpp 类似于 ChoiseNode 实现

    【讨论】:

    • @KeithM 谢谢。不知何故,我在快结束时搞砸了。
    • 应用您的解决方案解决了循环依赖问题,谢谢。
    【解决方案2】:

    returnOpposite() 方法的所有两个可重写对象应具有相同的返回类型。

    否则你的代码根本无法编译,这个抽象方法virtual Node&amp; returnOpposite() = 0;在派生类中将没有实现。

    所以你的类应该是这样的:

    class BaseNode {
        protected:
            virtual BaseNode* returnOpposite() = 0;
    }
    
    class ChoiceNode: public BaseNode {
        virtual BaseNode* returnOpposite() override;
    }
    
    class OpponentNode: public BaseNode {
        virtual BaseNode* returnOpposite() override;
    }
    

    更新(感谢@DaveS 评论):如果这两种类型都是协变的(如在您的草图中),那么以下将正常工作:

    class BaseNode {
        protected:
            virtual BaseNode* returnOpposite() = 0;
    }
    
    class OpponentNode; // forward declaration
    
    class ChoiceNode: public BaseNode {
        virtual OpponentNode* returnOpposite() override;
    }
    
    class OpponentNode: public BaseNode {
        virtual ChoiceNode* returnOpposite() override;
    }
    

    请注意,我在这里返回的是指向实例而不是引用的指针。

    【讨论】:

    • 这不太正确。 C++ 允许协变返回类型。如果不是因为依赖循环阻止他正确包含所有类,它会起作用。
    • 问题是BaseNode的虚函数应该有返回类型BaseNode。如果是这种情况,那么覆盖的返回类型是 BaseNode 的后代是完全合法的。毕竟所有ChoiceNodes 和所有OpponentNodes 实际上都是BaseNodes。
    • 我的错,Node& 而不是 BaseNode& 是一个错字
    • @DaveS +1 如果类型确实是协变的。
    • 当我尝试这样做时,我得到了无效的协变返回类型错误。仅使用前向声明,编译器不知道 Opponent/ChoiceNode 是从 BaseNode 派生的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-24
    • 2011-02-10
    • 2020-11-05
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 2022-11-27
    相关资源
    最近更新 更多