31
пропущено в производном классе, компилятор должен интерпретировать версию функции в
производном классе так, словно она и там была объявлена виртуальной. Я люблю называть подобную
логику работы компилятора DWIMNIS: «Do what I mean, not what I say» («Делай то, что я
подразумеваю, а не то, что я говорю»). Как правило, в C++ эта логика начисто отсутствует, поэтому ее
редкие проявления (как в данном случае) смотрятся неестественно. В следующем примере для обоих
указателей будет вызвана функция
Bar::Fn()
:
class Foo {
public:
virtual void Fn();
};
class Bar {
public:
void
Fn(); //
Все равно считается виртуальной
};
Bar* b = new Bar;
b->Fn();
//
Вызывает Bar::Fn()
Foo* f = b;
f->Fn();
//
Также вызывает Bar::Fn()
Подобные ситуации нежелательны по двум причинам. Во-первых, компилятор может неправильно
интерпретировать их, и тогда в последней строке будет вызвана функция
Foo::Fn()
. Во-вторых, ваши
коллеги ни за что не разберутся, почему в одном месте функция
Fn()
виртуальная, а в другом — нет.
После бессонной ночи они могут устроить погром в конторе.
Если в производном классе создается функция с тем же именем, но с другой сигнатурой, она скрывает
все сигнатуры базового класса для данной функции, но только в области действия производного класса.
Понятно? Нет? Что ж, вы не одиноки.
class Foo {
public:
virtual void Fn();
virtual void Fn(char*);
};
class Bar {
public:
virtual void Fn(int); // Можно, но не желательно
};
Вероятно, многие новички-программисты допоздна засиживались на работе, пытаясь разобраться в
происходящем. А происходит следующее:
•
При попытке вызвать
Fn()
через
Ваr*
доступной будет лишь одна сигнатура,
void Fn(int)
.
Обе версии базового класса скрыты и недоступны через
Bar*
.
•
При преобразовании
Bar*
в
Foo*
становятся доступными обе сигнатуры, объявленные в
Foo
,
но не сигнатура
void Fn(int)
. Более того, это не переопределение, поскольку сигнатура
Bar::Fn()
отличается от версии базового класса. Другими словами, ключевое слово
virtual
никак не влияет на работу этого фрагмента.
Если вам когда-нибудь захочется сделать нечто похожее, встаньте с кресла, медленно прогуляйтесь
вокруг дома, сделайте глубокий вдох, сядьте за компьютер и придумайте что-нибудь другое. Если уж
перегружать, то перегружайте все сигнатуры функции. Никогда не перегружайте часть сигнатур и
никогда не добавляйте новые сигнатуры в производный класс без переопределения всех сигнатур
функции базового класса. Если это покажется слишком сложным, запомните хорошее правило: когда
при чтении программы возникают вопросы, вероятно, ваше решение неудачное.
32
Друзья
Любой класс может объявить что-нибудь своим другом (
friend
). Друзья компилируются обычным
образом, за исключением того, что все защищенные и закрытые члены дружественного класса видны
так, словно друг является функцией этого класса. Друзьями можно объявлять функции — как
глобальные, так и члены классов. Классы тоже могут объявляться друзьями других классов; в этом
случае во всех функциях класса-друга «видны» все члены того класса, другом которого он является.
class Foo;
class BarBar {
public:
int
Fn(Foo*);
};
class Foo {
friend void GlobalFn();
//
Дружественные глобальные функции
friend class Bar;
// Дружественный класс
friend int BarBar::Fn(Foo*); //
Дружественная функция класса
friend class DoesNotExist; // См. Ниже
private:
int
x;
struct
ListNode
{
ListNode*
next;
void*
datum;
ListNode() : next(NULL), datum(NULL) {}
}
head;
protected:
int
y;
public:
void
G();
};
void GlobalFn()
{
Foo* f = new Foo;
f->x = 17;
// Разрешается из-за дружеских отношений
}
class Bar {
private:
Foo*
f;
public:
Bar() : f(new Foo) {}
void
WalkList();
};
void Bar::WalkList()
{
Foo::ListNode* n = f->head.next;
for (; n != NULL; n = n->next)
cout << n->datum << endl;
}
int BarBar::Fn(Foo* f)
{
return
f->x;
}
Do'stlaringiz bilan baham: |