重写虚函数时语法怪异

Weird syntax when overriding virtual functions

本文关键字:语法 函数 重写      更新时间:2023-10-16

读到这个答案,我很惊讶地自己尝试一下,如果它真的像描述的那样工作:

#include <iostream>
class A {
public:
    virtual void foo() = 0;
};
class B {
public:
    virtual void foo() = 0;
};
class C : public A, public B {
public:
    virtual void foo();
};
void C::foo(){
  std::cout << "C" << std::endl;
}
void C::A::foo(){
  std::cout << "A" << std::endl;
}
void C::B::foo(){
  std::cout << "B" << std::endl;
}
int main() {
    C c;
    static_cast<A*>(&c)->foo();
    static_cast<B*>(&c)->foo();
    c.foo();
    return 0;
}

我真的不认为可以从两个具有相同名称和签名的不同基类重写虚方法。并且,正如我所料,上面的程序打印:

C
C
C

所以答案是错误的——正如我从一开始就感觉到的那样。令人惊讶的部分是:为什么我的gcc接受这种语法:void C::A::foo(){ ?我找不到任何关于这可能意味着什么的东西。这是一个gcc bug/特性吗?是一些晦涩的标准c++语法吗?还是我完全误解了形势?

编辑:

在这种情况下,void C::A::foo(){}似乎只是A::foo的定义。但是为什么呢?这是GCC bug吗?或者这是标准所允许的?如果是这样,它是针对这种事情的特定规则,还是某种泛型子句(例如:如果标识符没有意义-例如'C::A::foo',那么编译器可以自由地做它想做的事情)。

我认为C::A::foo定义A::foo的事实是由于注入了class-name

N3337 [class]/2:在类名被看到之后,类名被插入到声明它的作用域中。类名也被插入到类本身的作用域中;这就是被注入的类名。出于访问检查的目的,注入的类名被视为公共成员名。[…]

由于AC的基类,并且A的名字被注入到A的作用域中,所以AC中也是可见的。

鉴于上述情况,我们可以做一些可怕的反常的事情,如:

void C::C::C::A::A::A::foo(){
    std::cout << "A" << std::endl;
}

现场演示

有趣的是,

void C::A::foo(){
  std::cout << "A" << std::endl;
}

定义了A::foo()(如果愿意,您可以提供纯虚函数的实现)。您可以通过添加一个附加定义来检查是否确实如此:

void A::foo(){
  std::cout << "Base A" << std::endl;
}
void C::A::foo(){
  std::cout << "A" << std::endl;
}

GCC将报告一个多重定义错误。这可能是有意义的,因为您可以将C::A视为A的名称别名。

就标准所说的而言,我目前正在努力寻找在标准海洋中何时合适的确切定义,但我可以在第3.4.3.1.2段中看到一个相关的例子:

struct A { A(); };
struct B: public A { B(); };
A::A() { }
B::B() { }
B::A ba; // object of type A // <-- here
A::A a; // error, A::A is not a type name
struct A::A a2; // object of type A

这个例子与构造函数解析有关,但是构造函数最终是一个方法,所以我猜GCC对每个作用域解析都使用了相同的机制。

Visual Studio编译器(2008)不允许

void C::A::foo(){
  std::cout << "A" << std::endl;
}
void C::B::foo(){
  std::cout << "B" << std::endl;

但只能

void A::foo(){
  std::cout << "A" << std::endl;
}
void B::foo(){
  std::cout << "B" << std::endl;

为纯虚方法提供定义是完全有效的。

"Effective c++"Meyers提到了使用纯虚函数的原因实现这个纯虚函数的派生类函数可以在其代码中的某处调用此实现。如果部分两个不同的派生类的代码是相似的在层次结构中向上移动它是有意义的,即使函数应该是纯虚拟。

请看这里(forum.codeguru.com)

纯虚函数不能动态调用,只能静态调用:

C c;
static_cast<A*>(&c)->foo(); // prints C
static_cast<B*>(&c)->foo(); // prints C, dynamic dispatch
c.foo();                    // prints C, dynamic dispatch
c.A::foo();                 // prints A, static dispatch
c.B::foo();                 // prints B, static dispatch

解释:

当你使用虚函数的全限定名类名后面跟着"::"),编译器不使用虚函数调用机制,而是使用与调用相同的机制非虚拟函数。换句话说,它按名称调用函数而不是通过槽号。如果你想在派生类里写代码调用Base::f(),即在其基中定义的f()的版本类的基础,你应该写:

void Derived::f()
{ Base::f(); }

请看这里(isocpp.org)