【问题标题】:How do I disambiguate overriding virtuals from a repeatedly inherited base class?如何从重复继承的基类中消除覆盖虚拟的歧义?
【发布时间】:2012-08-23 07:30:23
【问题描述】:

考虑以下无效的 C++ 代码。

#include <assert.h>

class NodeInterface {
public:
    virtual ~NodeInterface () {}

    virtual int f (const int& n) const = 0;
};

class ChildNodeInterface : public NodeInterface {
public:
    virtual ~ChildNodeInterface () {}
};

class ParentNodeInterface : public NodeInterface {
public:
    virtual ~ParentNodeInterface () {}
};

class ChildNode : public ChildNodeInterface {
public:
    virtual ~ChildNode () {}

    virtual int f (const int& n) const {
        return 2*n;
    }
};

class ParentNode : public ParentNodeInterface, private ChildNodeInterface {
public:
    explicit ParentNode () :
        mChild (new ChildNode ())
    {
    }

    virtual ~ParentNode () {}

    ChildNodeInterface* GetChildHandle () {
        return this;
    }

    virtual int f (const int& n) const {
        return 3*n;
    }

private:
    ChildNode* const mChild;

    // How do I specify that I would like to override ChildNodeInterface::f?
    virtual int f (const int& n) const { // On MSVC2010: C2535 member function already defined or declared
        return 1 + mChild->f (n);
    }
};

int main()
{
    ParentNode parent;
    assert (parent.f (2) == 6);
    ChildNode node;
    assert (node.f (2) == 4);
    ChildNodeInterface* child (parent.GetChildHandle ());
    assert (child->f (2) == 5);
    return 0;
}

我的目标是让ParentNode 私下看起来像ChildNode,这样它就可以在ChildNodeChildNodeInterface 实现之上添加一些额外的功能。因此,ParentNode 可以有效地被视为伪装的ChildNode 句柄,GetChildHandle 的简单性表明了这一点。 显然,如果ParentNode 不会重复从NodeInterface 继承,就没有问题。因为,人们可以很容易地消除覆盖的歧义。以下正确示例说明了这一点:

#include <assert.h>

class ChildNodeInterface {
public:
    virtual ~ChildNodeInterface () {}

    virtual int ChildMethod (const int& n) const = 0;
};

class ParentNodeInterface {
public:
    virtual ~ParentNodeInterface () {}

    virtual int ParentMethod (const int& n) const = 0;
};

class ChildNode : public ChildNodeInterface {
public:
    virtual ~ChildNode () {}

    virtual int ChildMethod (const int& n) const {
        return 2*n;
    }
};

class ParentNode : public ParentNodeInterface, private ChildNodeInterface {
public:
    explicit ParentNode () :
        mChild (new ChildNode ()),
        mValue (1)
    {
    }

    ChildNodeInterface* GetChildHandle () {
        return this;
    }

    virtual int ParentMethod (const int& n) const {
        return 3*n;
    }

private:
    ChildNode* const mChild;
    const int mValue;

    virtual int ChildMethod (const int& n) const {
        return mValue + mChild->ChildMethod (n);
    }
};

int main()
{
    ParentNode parent;
    assert (parent.ParentMethod (2) == 6);
    ChildNode node;
    assert (node.ChildMethod (2) == 4);
    ChildNodeInterface* child (parent.GetChildHandle ());
    assert (child->ChildMethod (2) == 5);
    return 0;
}

但是,在ParentNodeInterfaceChildNodeInterface 都继承自NodeInterface 的特殊情况下,就会出现歧义。 从main 的断言中应该可以清楚地看出,我的目标不是虚拟继承NodeInterface。我打算在ParentNode 中实现NodeInterface::f 的真正不同的实现。 我想知道如何(如果可能)在ParentNode 中区分ParentNodeInterface::fChildNodeInterface::f 的实现。

【问题讨论】:

    标签: c++ multiple-inheritance


    【解决方案1】:

    我认为你的设计是错误的。您继承了 ChildNodeInterface 并且您的 ParentNode 类中有 ChildNode 成员?

    继承的 ChildNodeInterface 不允许您使用 f 函数实现,因为 ChildNode 类不在继承树中。

    C++ 不允许重新定义具有相同签名的函数两次。所以不能写2个同一个虚函数的实现。

    所以你有两个选择:

    1. [最佳] 尝试以正确的方式使用继承重新定义您的类设计。钻石继承通常是由于设计不当造成的。
    2. [Fastest] 从 ChildNodeInterface 移除继承,并更喜欢在 ParentNode::f() 函数中调用 mChild->f() 以将 Child 行为添加到父级:

      virtual int f (const int& n) const { 返回 3*n + mChild->f(n); } 但不知道是不是你想要的行为。

    【讨论】:

    • 您说“C++ 不允许重新定义具有相同签名的函数两次”。我同意,但我不明白为什么这些虚拟机具有完全相同的签名。从ParentNode 可以看出,f 公开和私下(这是一个区别)以及通过ChildNodeInterfaceParentNodeInterface(这是另一个区别)发生。
    • 据我所知,可见性不是签名的一部分。从 ParentNode 的角度来看,可见性是相同的。除了菱形继承之外,您不能从公共基础重新定义虚函数,因为编译器无法知道原型来自哪个继承路径。你必须做虚拟继承才能做这种事情
    【解决方案2】:

    请注意,ChildNodeInterface 确实与 ChildNode::f() 无关。

    但要解决您的问题(我认为),不要私下从ChildNodeInterface 继承,而是使用从ChildNode 继承的帮助器类型并在该类型中覆盖ChildNode::f()ParentNode 可以维护一个指向其中一个对象的指针(或者只拥有该类型的普通旧成员),并在调用 ParentNode::GetChildHandle() 时分发指向该对象的指针:

    // the helper class
    class ParentNode;
    class ParentsChildNodeInterface : public ChildNode {
    public:
        virtual int f (const int& n) const;
    };
    
    class ParentNode : public ParentNodeInterface {
    
    public:
        explicit ParentNode () :
            mChild (new ParentsChildNodeInterface ())
        {
        }
    
        virtual ~ParentNode () {}
    
        ChildNodeInterface* GetChildHandle () {
            return mChild;
        }
    
        virtual int f (const int& n) const {
            return 3*n;
        }
    
    private:
        ParentsChildNodeInterface* const mChild;
    };
    
    // the new, special f() for children of parents...
    int ParentsChildNodeInterface::f (const int& n) const {
        return 1 + ChildNode::f (n);
    }
    

    正如所发布的,帮助类不需要访问ParentNode 对象中的任何状态。但如果确实需要,您可以将帮助程序类设为 friendParentNode

    【讨论】:

    • 确实,辅助类可以解决问题,但需要与ParentNode 建立友谊才能访问状态。这是不可取的。我刚刚添加了一个工作示例来说明这种考验的目的。现在可以看出,只有NodeInterface 的重复继承引起的歧义才会导致问题。
    • 助手类是ParentNode的实现细节。将助手设置为friend 在某种意义上可能是不可取的,但这只是反映了一个事实,即它们之间存在密切的植入关系。您可以通过在 ParentNode 中声明帮助程序类来加强“实现细节”方面,使其成为私有的。或者转发声明助手类,将其标记为friend 并将整个实现(包括助手类的定义)放入ParentNode.cpp 实现文件中 - 没有其他人可以访问助手类。
    猜你喜欢
    • 2012-11-25
    • 2012-02-05
    • 2011-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多