Странный синтаксис при переопределении виртуальных функций
Чтение этого ответа, я был достаточно удивлен, чтобы попробовать себя, если он действительно работает, как описано:
#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? Это какой-то неясный стандартный синтаксис С++? Или я полностью неверно истолковал ситуацию?
EDIT:
Кажется, что void C::A::foo(){}
- это просто определение для A::foo
в этом контексте. Но почему? Это ошибка GCC? Или это как-то разрешено стандартом? Если да, то это конкретное правило для такого рода вещей или какое-то общее предложение (скажем: если идентификатор не имеет смысла - например, "C:: A:: foo", тогда компилятор может делать то, что он хочет).
Ответы
Ответ 1
Я думаю, что факт, что C::A::foo
определяет A::foo
, вызван именем injected-class.
N3337 [class]/2:
Имя класса вставляется в область, в которой она объявляется сразу после просмотра имени класса. Имя класса также вставляется в область самого класса; это известно как имя введенного класса. Для проверки доступа имя введенного класса рассматривается как имя публичного участника. [...]
Так как A
является базовым классом C
и имя A
вводится в область A
, A
также видно из C
.
Учитывая вышеизложенное, мы можем делать ужасно извращенные вещи, такие как:
void C::C::C::A::A::A::foo(){
std::cout << "A" << std::endl;
}
Live Demo
Ответ 2
Интересно, что
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 использовал один и тот же механизм для каждого разрешения области.
Ответ 3
Компилятор 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;
поскольку он полностью действителен, чтобы дать определение для чистых виртуальных методов.
"Эффективный С++" Майерс упоминает причину чистой виртуальной функции иметь тело: производные классы, реализующие этот чистый виртуальный функция может вызывать эту реализацию smwhere в своем коде. Если часть кода двух разных производных классов аналогичны, то это делает смысл перемещать его в иерархии, даже если функция должна быть чистый виртуальный.
Смотрите здесь (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
Пояснение:
Когда вы вызываете виртуальную функцию, используя ее полное имя ( class-name, за которым следует "::" ), компилятор не использует виртуальную но вместо этого использует тот же механизм, как если бы вы вызывали не виртуальная функция. С другой стороны, он вызывает функцию по имени а не слотом. Поэтому, если вы хотите, чтобы код в производном классе Der, чтобы вызвать Base:: f(), то есть версию f(), определенную в ее базе класс Base, вы должны написать:
void Derived::f()
{ Base::f(); }
См. здесь (isocpp.org).