假设我们要写一个应用程序,它可以把消息传送到几个不同的公司去。消息既可以以加密方式也可以以明文(不加密)的方式传送。如果我们有足够的信息在编译期间确定哪个消息将要发送给哪个公司,我们就可以用一个 template-based(模板基)来解决问题:
class CompanyA {
public:
...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
class CompanyB {
public:
...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
... // classes for other companies
class MsgInfo { ... }; // class for holding information
// used to create a message
template
class MsgSender {
public:
... // ctors, dtor, etc.
void sendClear(const MsgInfo& info)
{
std::string msg;
create msg from info;
Company c;
c.sendCleartext(msg);
}
void sendSecret(const MsgInfo& info) // similar to sendClear, except
{ ... } // calls c.sendEncrypted
};
template
class LoggingMsgSender: public MsgSender
public:
... // ctors, dtor, etc.
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
sendClear(info); // call base class function;
// this code will not compile!
write "after sending" info to the log;
}
...
};
注意 derived class(派生类)中的 message-sending function(消息发送函数)的名字 (sendClearMsg) 与它的 base class(基类)中的那个(在那里,它被称为 sendClear)不同。这是一个好的设计,因为它避开了 hiding inherited names(隐藏继承来的名字)的问题(参见《C++箴言:避免覆盖通过继承得到的名字》)和重定义一个 inherited non-virtual function(继承来的非虚拟函数)的与生俱来的问题(参见《C++箴言:绝不重定义继承的非虚拟函数》)。但是上面的代码不能通过编译,至少在符合标准的编译器上不能。这样的编译器会抱怨 sendClear 不存在。我们可以看见 sendClear 就在 base class(基类)中,但编译器不会到那里去寻找它。我们有必要理解这是为什么。
问题在于当编译器遇到 class template(类模板)LoggingMsgSender 的 definition(定义)时,它们不知道它从哪个 class(类)继承。当然,它是 MsgSender
为了使问题具体化,假设我们有一个要求加密通讯的 class(类)CompanyZ:
public: // sendCleartext function
...
void sendEncrypted(const std::string& msg);
...
}; 一般的 MsgSender template(模板)不适用于 CompanyZ,因为那个模板提供一个 sendClear function(函数)对于 CompanyZ objects(对象)没有意义。为了纠正这个问题,我们可以创建一个 MsgSender 针对 CompanyZ 的特化版本:
template<>// a total specialization of
class MsgSender
public: // general template, except
... // sendCleartext is omitted
void sendSecret(const MsgInfo& info)
{ ... }
};
已知 MsgSender 针对 CompanyZ 被特化,再次考虑 derived class(派生类)LoggingMsgSender:
template
class LoggingMsgSender: public MsgSender
public:
...
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
sendClear(info); // if Company == CompanyZ,
// this function doesn't exist!
write "after sending" info to the log;
}
...
};
为了重新启动它,我们必须以某种方式使 C++ 的 "don't look in templatized base classes"(不在模板基类中寻找)行为失效。有三种方法可以做到这一点。首先,你可以在调用 base class functions(基类函数)的前面加上 "this->":
template
class LoggingMsgSender: public MsgSender
public:
...
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
this->sendClear(info); // okay, assumes that
// sendClear will be inherited
write "after sending" info to the log;
}
...
};
template
class LoggingMsgSender: public MsgSender
public:
using MsgSender
... // that sendClear is in the
// base class
void sendClearMsg(const MsgInfo& info)
{
...
sendClear(info); // okay, assumes that
... // sendClear will be inherited
}
...
};
最后一个让你的代码通过编译的办法是显式指定被调用的函数是在 base class(基类)中的:
template
class LoggingMsgSender: public MsgSender
public:
...
void sendClearMsg(const MsgInfo& info)
{
...
MsgSender
... // sendClear will be
} // inherited
...
};
从名字可见性的观点来看,这里每一个方法都做了同样的事情:它向编译器保证任何后继的 base class template(基类模板)的 specializations(特化)都将支持 general template(通用模板)提供的 interface(接口)。所有的编译器在解析一个像 LoggingMsgSender 这样的 derived class template(派生类模板)是,这样一种保证都是必要的,但是如果保证被证实不成立,真相将在后继的编译过程中暴露。例如,如果后面的源代码中包含这些,
LoggingMsgSender
MsgInfo msgData;
... // put info in msgData
zMsgSender.sendClearMsg(msgData); // error! won't compile
从根本上说,问题就是编译器是早些(当 derived class template definitions(派生类模板定义)被解析的时候)诊断对 base class members(基类成员)的非法引用,还是晚些时候(当那些 templates(模板)被特定的 template arguments(模板参数)实例化的时候)再进行。C++ 的方针是宁愿早诊断,而这就是为什么当那些 classes(类)被从 templates(模板)实例化的时候,它假装不知道 base classes(基类)的内容。
Things to Remember
·在 derived class templates(派生类模板)中,可以经由 "this->" 前缀引用 base class templates(基类模板)中的名字,经由 using declarations,或经由一个 explicit base class qualification(显式基类限定)。
