... // классы для других компаний
class MsgInfo {...}; // класс, содержащий информацию,
// используемую для создания
// сообщения
template
class MsgSender {
public:
... // конструктор, деструктор и т. п.
void sendClear(const MsgInfo& info)
{
std::string msg;
Company c;
c.sendClearText(msg);
}
void sendSecret(const MsgInfo& info) // аналогично sendClear, но вызывает
{...} // c.sendEncrypted
};
Эта программа будет работать. Но предположим, что иногда мы хотим протоколировать некоторую информацию при отправке сообщений. Такую возможность легко добавить, написав производный класс, и, на первый взгляд, разумно это сделать следующим образом:
template
class LoggingMsgSender: public MsgSender
public:
...
void sendClearMsg(const MsgInfo& info)
{
sendClear(info); // вызвать функцию из базового класса
//
}
...
};
Отметим, что функция, отправляющая сообщение, в производном классе называется иначе (sendClearMsg), чем в базовом (sendClear). Это хорошее решение, потому что таким образом мы обходим проблему сокрытия унаследованных имен (см. правило 33), а равно сложности, возникающие при переопределении наследуемых невиртуальных функций (см. правило 36). Но этот код не будет компилироваться, по крайней мере, компилятором, совместимым со стандартом. Такой компилятор решит, что функции sendClear не существует. Мы видим, что эта функция определена в базовом классе, но компилятор не станет искать ее там. Попытаемся понять – почему.
Проблема в том, что когда компилятор встречает определение шаблона класса LoggingMsgSender, он не знает, какому классу тот наследует. Понятно, что классу MsgSender
Чтобы яснее почувствовать, в чем сложность, предположим, что у нас есть класс CompanyZ, описывающий компанию, которая настаивает на том, чтобы все сообщения шифровались:
class CompanyZ { // этот класс не представляет
public: // функции sendCleartext
...
void sendEncrypted(const std::string& msg);
...
};
Общий шаблон MsgSender не подходит для CompanyZ, потому что в нем определена функция sendClear, которая для объектов класса CompanyZ не имеет смысла. Чтобы решить эту проблему, мы можем создать специализированную версию MsgSender для CompanyZ:
template <> // полная специализация MsgSender;
class MsgSender
public: // только отсутствием функции
... // sendCleartext
void sendSecret(const MsgInfo& info)
{...}
};
Обратите внимание на синтаксическую конструкцию «template<>» в начале определения класса. Она означает, что это и не шаблон, и не автономный класс. Это специализированная версия шаблона MsgSender, которая должна использоваться, если параметром шаблона является CompanyZ. Называется это
Имея специализацию шаблона MsgSender для CompanyZ, снова рассмотрим производный класс LoggingMsgSender:
template
class LoggingMsgSender: public MsgSender
public:
...
void sendClearMsg(const MsgInfo& info)
{
sendClear(info); // если Company == CompanyZ,
// то этой функции не существует