Читаем Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ полностью

Обратите внимание на синтаксическую конструкцию «template» в начале определения класса. Она означает, что это и не шаблон, и не автономный класс. Это специализированная версия шаблона MsgSender, которая должна использоваться, если параметром шаблона является CompanyZ. Называется это полной специализацией шаблона : шаблон MsgSender специализирован для типа CompanyZ, и эта специализация применяется, коль скоро в качестве параметра указан тип CompanyZ, никакие другие особенности параметров шаблона во внимание не принимаются.

Имея специализацию шаблона MsgSender для CompanyZ, снова рассмотрим производный класс LoggingMsgSender:


template typename Company

class LoggingMsgSender: public MsgSenderCompany {

public:

...

void sendClearMsg(const MsgInfo info)

{

записать в протокол перед отправкой;

sendClear(info); // если Company == CompanyZ,

// то этой функции не существует

записать в протокол после отправки;

}

...

};


Как следует из комментария, этот код просто не имеет смысла, если базовым классом является MsgSenderCompanyZ, так как в нем нет функции sendClear. Поэтому C++ отвергнет такой вызов; компилятор понимает, что шаблон базового класса можно специализировать, и интерфейс, предоставляемый этой специализацией, может быть не таким, как в общем шаблоне. В результате компилятор обычно не ищет унаследованные имена в шаблонных базовых классах. В некотором смысле, когда мы переходим от «объектно-ориентированного C++» к «C++ с шаблонами» (см. правило 1), наследование перестает работать.

Чтобы исправить ситуацию, нужно как-то заставить C++ отказаться от догмы «не заглядывай в шаблонные базовые классы». Добиться этого можно тремя способами. Во-первых, можно предварить обращения к функциям из базового класса указателем this:


template typename Company

class LoggingMsgSender: public MsgSenderCompany {

public:

...

void sendClearMsg(const MsgInfo info)

{

записать в протокол перед отправкой

;

this-sendClear(info); // порядок! Предполагается, что

// sendClear будет унаследована

записать в протокол после отправки

;

}

...

};


Во-вторых, можно воспользоваться using-объявлением. Мы уже обсуждали эту тему в правиле 33, где было сказано, что using-объявлением делает скрытые имена из базового класса видимыми в производном классе. Поэтому мы можем переписать sendClearMsg следующим образом:


template typename Company

class LoggingMsgSender: public MsgSenderCompany {

public:

using MsgSenderCompany::sendClear; // сообщает компилятору о том, что

... // sendClear есть в базовом классе

void sendClearMsg(const MsgInfo info)

{

...

sendClear(info); // нормально, предполагается, что

... // sendClear будет унаследована

}

...

};


Хотя using-объявление будет работать как здесь, так и в правиле 33, но используются они для решения разных задач. Здесь проблема не в том, что имена из базового класса скрыты за именами, объявленными в производном классе, а в том, что компилятор вообще не станет производить поиск в области видимости базового класса, если только вы явно не попросите его об этом.

И последний способ заставить ваш код компилироваться – явно указать, что вызываемая функция находится в базовом классе:


template typename Company

class LoggingMsgSender: public MsgSenderCompany {

pubilc:

...

void sendClearMsg(const MsgInfo info)

{

...

MsgSenderCompany::sendClear(info); // нормально, предполагается, что

... // sendClear будет унаследована

}

...

};


Но этот способ хуже прочих, посколько если вызываемая функция виртуальна, то явная квалификация отключает динамическое связывание.

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже