【问题标题】:C++ inherit from multiple base classes with the same virtual function nameC++从多个具有相同虚函数名的基类继承
【发布时间】:2013-08-26 05:51:34
【问题描述】:

我试过这段代码:

class A
{
    virtual void foo() = 0;
};

class B
{
    virtual void foo() = 0;
};

class C : public A, public B
{
    //virtual void A::foo(){}
    //virtual void B::foo(){}

    virtual void A::foo();
    virtual void B::foo();
};

void C::A::foo(){}
void C::B::foo(){}

int main()
{
    C c;
    return 0;
}

使用注释部分是可以的,但是当我尝试在类声明之外编写定义时,编译器会报错。 我正在使用 MSVC11 编译器,有人知道如何编写吗? 我需要将代码移动到 cpp 文件中。

谢谢~~

【问题讨论】:

  • 注释部分在 gcc 中也不起作用。
  • 这完全没有意义。它应该只是virtual void foo();,并且只有一次
  • 你想如何使用ABC?有很多可能性:example 1example 2、...(那些可能定义了不必要的函数)
  • [dcl.meaning]/1 禁止在(成员)函数的声明中使用 qualified-id:“当 declarator-id 被限定时,声明应引用限定符所引用的类或命名空间的先前声明的成员[...]”;因此任何virtual void X::foo(); 都是非法的(作为C 中的声明)。
  • 实际上在DyP's remark 之后,我之前评论中链接的代码具有误导性,因为C::A::foo 真正的意思是A::foo(感谢@DyP)。以下是更简单的示例:ideone.com/HCYM1Xideone.com/vdRp3X

标签: c++ function class virtual multiple-inheritance


【解决方案1】:

函数根据名称和参数类型覆盖基类的虚函数(见下文)。因此,您的类C两个 虚函数foo,一个继承自AB。但是函数void C::foo() 覆盖了both

[class.virtual]/2

如果在类Base和类Derived中声明了一个虚拟成员函数vf,则直接或间接派生自Base,则一个同名的成员函数vf parameter-type-list、cv-qualification 和 ref-qualifier(或不存在相同)作为 Base::vf 被声明,然后 Derived::vf 也是虚拟的(无论是否如此声明)并且它 覆盖 Base::vf.

正如我在 cmets 中所述,[dcl.meaning]/1 禁止在(成员)函数的声明中使用 qualified-id

declarator-id 被限定时,该声明应引用该限定符所引用的类或命名空间的先前声明的成员 [...]"

因此,任何virtual void X::foo(); 作为C 中的声明都是非法的。

代码

class C : public A, public B
{
    virtual void foo();
};

是 AFAIK 覆盖foo 的唯一方法,它将同时覆盖A::fooB::foo。除了引入另一层继承之外,没有办法为 A::fooB::foo 设置两个不同的覆盖行为:

#include <iostream>

struct A
{
    virtual void foo() = 0;
};

struct B
{
    virtual void foo() = 0;
};

struct CA : A
{
    virtual void foo() { std::cout << "A" << std::endl; }
};

struct CB : B
{
    virtual void foo() { std::cout << "B" << std::endl; }
};

struct C : CA, CB {};

int main() {
    C c;
    //c.foo();  // ambiguous

    A& a = c;
    a.foo();

    B& b = c;
    b.foo();
}

【讨论】:

  • 如果CA::fooCB::foo 都需要访问对象内的共享数据,你可能需要一个third 基类来包含共享部分——然后你会得到可怕的钻石遗产。真是一团糟!
  • 请注意,歧义可以明确解决:c.A::foo();
  • 如果我将virtual 放在所有foo 方法之前或只放一次有什么区别?如果我将 virtual 放入方法 A::foo 或 B::foo 或 CA::foo 等,这有关系吗?还是无关紧要?
  • @CătălinaSîrbu 如果您明确编写 virtual 或者如果它与任何基类中的 virtual 函数具有相同的签名(名称、参数),则该函数是虚拟的。所以这里的大多数virtual 关键字都是多余的。我将 virtual 添加到所有虚拟函数之前——即使没有必要——直接表明该函数是虚拟的。
【解决方案2】:

你只有一个虚函数foo

class A {
    virtual void foo() = 0;
};

class B {
    virtual void foo() = 0;
};

class C : public A, public B {
    virtual void foo();

};

void C::foo(){}
void C::A::foo(){}
void C::B::foo(){};

int main() {
    C c;
    return 0;
}

【讨论】:

  • 备注: 定义 void C::A::foo(){}void C::B::foo(){} 分别为纯虚函数 A::fooB::foo 提供定义(并且不是必需的) .
  • dyp 所说:void C::foo() {cout&lt;&lt;"C"&lt;&lt;endl;} void C::A::foo() {cout&lt;&lt;"A"&lt;&lt;endl;} void C::B::foo() {cout&lt;&lt;"B"&lt;&lt;endl;}; int main() { C c; c.foo(); ( (A&amp;)c ).foo(); ( (B&amp;)c ).foo(); return 0; } 总是输出'C'
  • @dyp 只是好奇为什么使用语法void C::A::foo(){}A::foo() 提供定义是合法的,使用这种语法而不是直接覆盖A::for(){ } 有什么好处?我看起来很困惑,如果A 是一个内部类怎么办?我编译了这段代码,很惊讶它是合法的。
  • @Dreamer 这是一般概念的结果。定义成员函数时,只需使用其中一种方式来引用成员的名称;当您想调用函数时,可以使用相同的引用方式,例如this-&gt;C::A::foo(); 名称 AC 的成员,因为类 A 隐式定义了一个名为 A 的成员,引用其自身(注入类名称)。由于继承,此成员 A::AC 内可见。
【解决方案3】:

我遇到了同样的问题,不小心打开了第二个线程。对此感到抱歉。对我有用的一种方法是在没有多重继承的情况下解决它。

#include <stdio.h>

class A
{
public:
    virtual void foo(void) = 0;
};

class B
{
public:
    virtual void foo(void) = 0;
};


class C
{
    class IA: public A
    {
        virtual void foo(void)
        {
            printf("IA::foo()\r\n");
        }
    };
    class IB: public B
    {
        virtual void foo(void)
        {
            printf("IB::foo()\r\n");
        }
    };

    IA m_A;
    IB m_B;
public:
    A* GetA(void)
    {
        return(&m_A);
    }

    B* GetB(void)
    {
        return(&m_B);
    }
};

诀窍是将派生自接口(A 和 B)的类定义为本地类(IA 和 IB),而不是使用多重继承。此外,如果需要,这种方法还打开了每个接口的多个实现的选项,而这在使用多重继承时是不可能的。 本地类 IA 和 IB 可以很容易地访问类 C,因此接口 IA 和 IB 的实现可以共享数据。

各接口的访问方式如下:

main()
{
    C test;
    test.GetA()->foo();
    test.GetB()->foo();
}

...关于 foo 方法不再有歧义。

【讨论】:

    【解决方案4】:

    您可以使用不同的函数参数来解决这种歧义。

    在现实世界的代码中,这样的虚函数会做一些事情,所以它们通常已经有:

    1. A 和 B 中的不同参数,或
    2. 为了解决这个继承问题,你可以将A和B中不同的返回值变成[out]参数;否则
    3. 您需要添加一些标签参数,优化器会丢弃这些参数。

    (在我自己的代码中,我通常发现自己在情况(1)中,有时在(2)中,到目前为止从未在(3)中。)

    您的示例是案例 (3),如下所示:

    class A
    {
    public:
        struct tag_a { };
        virtual void foo(tag_a) = 0;
    };
    
    class B
    {
    public:
        struct tag_b { };
        virtual void foo(tag_b) = 0;
    };
    
    class C : public A, public B
    {
        void foo(tag_a) override;
        void foo(tag_b) override;
    };
    

    【讨论】:

      【解决方案5】:

      比阿迪戈斯汀溶液略有改善:

      
      #include <iostream>
      
      struct A {
          virtual void foo() = 0;
      };
      
      struct B {
          virtual void foo() = 0;
      };
      
      template <class T> struct Tagger : T {
          struct tag {};
          void foo() final { foo({}); }
          virtual void foo(tag) = 0;
      };
      
      using A2 = Tagger<A>;
      using B2 = Tagger<B>;
      
      struct C : public A2, public B2 {
          void foo(A2::tag) override { std::cout << "A" << std::endl; }
          void foo(B2::tag) override { std::cout << "B" << std::endl; }
      };
      
      int main() {
          C c;
          A* pa = &c;
          B* pb = &c;
          pa->foo(); // A
          pb->foo(); // B
          return 0;
      }
      

      假设基类AB已经给定且不能修改。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-07-02
        • 2011-05-29
        • 1970-01-01
        • 1970-01-01
        • 2013-01-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多