struct Shape {
// ...
virtual void draw_lines() const;
virtual void move();
// ...
};
virtual void Shape::draw_lines() const { /* ... */ } // ошибка
void Shape::move() { /* ... */ } // OK
14.3.3. Замещение
Если вы хотите заместить виртуальную функцию, то должны использовать точно такое же имя и типы аргументов, как и в базовом классе. Рассмотрим пример.
struct Circle:Shape {
void draw_lines(int) const; // возможно, ошибка (аргумент int?)
void drawlines() const; // возможно, ошибка (опечатка
в имени?)
void draw_lines(); // возможно, ошибка (нет const?)
// ...
};
В данном случае компилятор увидит три функции, независимые от функции
Shape::draw_lines()
(поскольку они имеют другие имена или другие типы аргументов), и не будет их замещать. Хороший компилятор предупредит программиста о возможных ошибках. В данном случае нет никаких признаков того, что вы действительно собирались замещать виртуальную функцию.
Пример функции
draw_lines()
реален, и, следовательно, его трудно описать очень подробно, поэтому ограничимся чисто технической иллюстрацией замещения.
struct B {
virtual void f() const { cout << "B::f "; }
void g() const { cout << "B::g "; } // невиртуальная
};
struct D : B {
void f() const { cout << "D::f "; } // замещает функцию B::f
void g() { cout << "D::g "; }
};
struct DD : D {
void f() { cout << "DD::f "; } // не замещает функцию D::f
(нет const)
void g() const { cout << "DD::g "; }
};
Здесь мы описали небольшую иерархию классов с одной виртуальной функцией
f()
. Мы можем попробовать использовать ее. В частности, можем попробовать вызвать функцию
f()
и невиртуальную функцию
g()
, не знающую конкретного типа объекта, который она должна вывести на печать, за исключением того, что он относится либо к классу
B
, либо к классу, производному от класса
B
.
void call(const B& b)
// класс D — разновидность класса B,
// поэтому функция call() может
// получить объект класса D
// класс DD — разновидность класса D,
// а класс D — разновидность класса B,
// поэтому функция call() может получать объект класса DD
{
b.f();
b.g();
}
int main()
{
B b;
D d;
DD dd;
call(b);
call(d);
call(dd);
b.f();
b.g();
d.f();
d.g();
dd.f();
dd.g();
}
В результате выполнения этой программы получим следующее:
B::f B::g D::f B::g D::f B::g B::f B::g D::f D::g DD::f DD::g
Если вы понимаете, почему, то знаете механизмы наследования и виртуальных функций.
14.3.4. Доступ
Язык С++ реализует простую модель доступа к членам класса. Члены класса могут относиться к следующим категориям.
• Закрытые (private). Если член класса объявлен с помощью ключевого слова
private
, то его имя могут использовать только члены данного класса.
• Защищенные (protected). Если член класса объявлен с помощью ключевого слова
protected
, то его имя могут использовать только члены данного класса или члены классов, производных от него.
• Открытые (public). Если член класса объявлен с помощью ключевого слова
public
, то его имя могут использовать все функции.
Изобразим это на рисунке.
Базовый класс также может иметь атрибут
private
,
protected
или
public
.
• Если базовый класс для класса
D
является закрытым, то имена его открытых и защищенных членов могут использоваться только членами класса
D
.
• Если базовый класс для класса
D
является защищенным, то имена его открытых и защищенных членов могут использоваться только членами класса
D
и членами классов, производных от класса
D
.
• Если базовый класс для класса
D
является открытым, то имена его открытых членов могут использоваться любыми функциями.