Ответ 1
Вы всегда можете (*) ссылаться на функцию базового класса с помощью идентификатора qualit:
#include <iostream>
class Base{
public:
void foo(){std::cout<<"base";}
};
class Derived : public Base
{
public:
void foo(){std::cout<<"derived";}
};
int main()
{
Derived bar;
//call Base::foo() from bar here?
bar.Base::foo(); // using a qualified-id
return 0;
}
[Также исправлены некоторые опечатки OP.]
(*) Ограничения доступа по-прежнему применяются, а базовые классы могут быть неоднозначными.
Если Base::foo
не virtual
, то Derived::foo
не переопределяет Base::foo
. Скорее, Derived::foo
скрывает Base::foo
. Разницу можно увидеть в следующем примере:
struct Base {
void foo() { std::cout << "Base::foo\n"; }
virtual void bar() { std::cout << "Base::bar\n"; }
};
struct Derived : Base {
void foo() { std::cout << "Derived::foo\n"; }
virtual void bar() { std::cout << "Derived::bar\n"; }
};
int main() {
Derived d;
Base* b = &d;
b->foo(); // calls Base::foo
b->bar(); // calls Derived::bar
}
(Derived::bar
неявно виртуально, даже если вы не используете ключевое слово virtual
, если оно подпишится на Base::bar
.)
Квалифицированный идентификатор является либо формой X :: Y
, либо просто :: Y
. Часть перед ::
указывает, где мы хотим найти идентификатор Y
. В первом виде мы смотрим вверх X
, затем смотрим вверх Y
из контекста X
. Во второй форме мы просматриваем Y
в глобальном пространстве имен.
Неквалифицированный-id не содержит ::
и поэтому не определяет (сам) контекст, где искать имя.
В выражении b->foo
оба b
и foo
являются неквалифицированными-идентификаторами. b
просматривается в текущем контексте (который в приведенном выше примере является функцией main
). Найдем локальную переменную Base* b
. Поскольку b->foo
имеет форму доступа к члену класса, мы смотрим foo
из контекста типа b
(или, скорее, *b
). Поэтому мы смотрим foo
из контекста Base
. Мы найдем функцию-член void foo()
, объявленную внутри Base
, которую я назову как Base::foo
.
Для foo
, мы закончили и вызываем Base::foo
.
Для b->bar
мы сначала находим Base::bar
, но объявляется virtual
. Поскольку это virtual
, мы выполняем виртуальную отправку. Это вызовет конечную функцию overrider в иерархии классов типа объекта b
. Поскольку b
указывает на объект типа Derived
, конечный переадресатор Derived::bar
.
При поиске имени foo
из Derived
контекста мы найдем Derived::foo
. Вот почему Derived::foo
, как говорят, скрывает Base::foo
. Выражения, такие как d.foo()
или внутри функции-члена Derived
, используя просто foo()
или this->foo()
, будут искать из контекста Derived
.
При использовании квалифицированного идентификатора мы явно указываем, где искать имя. Выражение Base::foo
указывает, что мы хотим найти имя foo
из контекста Base
(он может найти функции, которые Base
унаследовал, например). Кроме того, он отключает виртуальную отправку.
Следовательно, d.Base::foo()
найдет Base::foo
и назовет его; d.Base::bar()
найдет Base::bar
и назовет его.
Интересный факт: чистые виртуальные функции могут иметь реализацию. Они не могут быть вызваны через виртуальную отправку, потому что их нужно переопределить. Тем не менее, вы все равно можете вызвать их реализацию (если они есть), используя идентификатор с квалификацией.
#include <iostream>
struct Base {
virtual void foo() = 0;
};
void Base::foo() { std::cout << "look ma, I'm pure virtual!\n"; }
struct Derived : Base {
virtual void foo() { std::cout << "Derived::foo\n"; }
};
int main() {
Derived d;
d.foo(); // calls Derived::foo
d.Base::foo(); // calls Base::foo
}
Обратите внимание, что спецификаторы доступа как для членов класса, так и для базовых классов влияют на то, можно ли использовать функцию для определения типа базового класса для объекта производного типа.
Например:
#include <iostream>
struct Base {
public:
void public_fun() { std::cout << "Base::public_fun\n"; }
private:
void private_fun() { std::cout << "Base::private_fun\n"; }
};
struct Public_derived : public Base {
public:
void public_fun() { std::cout << "Public_derived::public_fun\n"; }
void private_fun() { std::cout << "Public_derived::private_fun\n"; }
};
struct Private_derived : private Base {
public:
void public_fun() { std::cout << "Private_derived::public_fun\n"; }
void private_fun() { std::cout << "Private_derived::private_fun\n"; }
};
int main() {
Public_derived p;
p.public_fun(); // allowed, calls Public_derived::public_fun
p.private_fun(); // allowed, calls Public_derived::public_fun
p.Base::public_fun(); // allowed, calls Base::public_fun
p.Base::private_fun(); // NOT allowed, tries to name Base::public_fun
Private_derived r;
r.Base::public_fun(); // NOT allowed, tries to call Base::public_fun
r.Base::private_fun(); // NOT allowed, tries to name Base::private_fun
}
Доступность ортогональна поиску имен. Таким образом, скрытие имени не влияет на него (вы можете оставить public_fun
и private_fun
в производных классах и получить одинаковое поведение и ошибки для вызовов с квалифицированным идентификатором).
Ошибка в p.Base::private_fun()
отличается от ошибки в r.Base::public_fun()
тем самым: первый уже не ссылается на имя Base::private_fun
(потому что это личное имя). Второй не может преобразовать r
из Private_derived&
в Base&
для this
-потока (по существу). Вот почему вторая работает из Private_derived
или друга Private_derived
.