C++箴言:如何会见模板化基类中的名字
假设我们要写一个应用措施,它可以把动静传送到几个差异的公司去。动静既可以以加密方法也可以以明文(不加密)的方法传送。假如我们有足够的信息在编译期间确定哪个动静将要发送给哪个公司,我们就可以用一个 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<typename Company> 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 }; |
这个可以或许很好地事情,可是假设我们有时需要在每次发送动静的时候把一些信息记录到日志中。通过一个 derived class(派生类)可以很简朴地增加这个成果,下面这个好像是一个公道的要领:
template<typename Company> class LoggingMsgSender: public MsgSender<Company> { 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<Company>,可是 Company 是一个 template parameter(模板参数),这个直到更迟一些才气被确定(当 LoggingMsgSender 被实例化的时候)。不知道 Company 是什么,就没有步伐知道 class(类)MsgSender<Company> 是什么样子的。出格是,没有步伐知道它是否有一个 sendClear function(函数)。
为了使问题详细化,假设我们有一个要求加密通讯的 class(类)CompanyZ:
class CompanyZ { // this class offers no 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<CompanyZ> { // MsgSender; the same as the public: // general template, except … // sendCleartext is omitted void sendSecret(const MsgInfo& info) { … } }; |
留意这个 class definition(类界说)开始处的 "template <>" 语法。它暗示这既不是一个 template(模板),也不是一个 standalone class(独立类)。正确的说法是,它是一个用于 template argument(模板参数)为 CompanyZ 时的 MsgSender template(模板)的 specialized version(特化版本)。这以 total template specialization(完全模板特化)闻名:template(模板)MsgSender 针对范例 CompanyZ 被特化,并且这个 specialization(特化)是 total(完全)的——只要 type parameter(范例参数)被界说成了 CompanyZ,就没有剩下能被改变的其它 template’s parameters(模板参数)。
已知 MsgSender 针对 CompanyZ 被特化,再次思量 derived class(派生类)LoggingMsgSender:
template<typename Company> class LoggingMsgSender: public MsgSender<Company> { 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; } … }; |
就像注释中写的,当 base class(基类)是 MsgSender<CompanyZ> 时,这里的代码是无意义的,因为谁人类没有提供 sendClear function(函数)。这就是为什么 C++ 拒绝这个挪用:它承认 base class templates(基类模板)可以被特化,而这个特化不必然提供和 general template(通用模板)沟通的 interface(接口)。功效,它凡是会拒绝在 templatized base classes(模板化基类)中寻找 inherited names(担任来的名字)。在某种意义上,当我们从 Object-oriented C++ 超过到 Template C++,inheritance(担任)会遏制事情。
为了从头启动它,我们必需以某种方法使 C++ 的 "don’t look in templatized base classes"(不在模板基类中寻找)行为失效。有三种要领可以做到这一点。首先,你可以在挪用 base class functions(基类函数)的前面加上 "this->":
template<typename Company> class LoggingMsgSender: public MsgSender<Company> { 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; } … }; |
第二,你可以利用一个 using declaration,假如你已经读过《C++箴言:制止包围通过担任获得的名字》,这应该是你很熟悉的一种办理方案。该文表明白 using declarations 如何将被埋没的 base class names(基类名字)引入到一个 derived class(派生类)规模中。因此我们可以这样写 sendClearMsg:
template<typename Company> class LoggingMsgSender: public MsgSender<Company> { public: using MsgSender<Company>::sendClear; // tell compilers to assume … // that sendClear is in the // base class void sendClearMsg(const MsgInfo& info) { … sendClear(info); // okay, assumes that … // sendClear will be inherited } … }; |
(固然 using declaration 在这里和《C++箴言:制止包围通过担任获得的名字》中都可以事情,但要办理的问题是差异的。这里的景象不是 base class names(基类名字)被 derived class names(派生类名字)埋没,而是假如我们不汇报它去做,编译器就不会搜索 base class 规模。)
最后一个让你的代码通过编译的步伐是显式指定被挪用的函数是在 base class(基类)中的:
template<typename Company> class LoggingMsgSender: public MsgSender<Company> { public: … void sendClearMsg(const MsgInfo& info) { … MsgSender<Company>::sendClear(info); // okay, assumes that … // sendClear will be } // inherited … }; |
凡是这是一个办理这个问题的最不合人心的要领,因为假如被挪用函数是 virtual(虚拟)的,显式限定会封锁 virtual binding(虚拟绑定)行为。
从名字可见性的概念来看,这里每一个要领都做了同样的工作:它向编译器担保任何后继的 base class template(基类模板)的 specializations(特化)都将支持 general template(通用模板)提供的 interface(接口)。所有的编译器在理会一个像 LoggingMsgSender 这样的 derived class template(派生类模板)是,这样一种担保都是须要的,可是假如担保被证实不创立,真相将在后继的编译进程中袒露。譬喻,假如后头的源代码中包括这些,
LoggingMsgSender<CompanyZ> zMsgSender; MsgInfo msgData; … // put info in msgData zMsgSender.sendClearMsg(msgData); // error! won’t compile |
对 sendClearMsg 的挪用将不能编译,因为在而今,编译器知道 base class(基类)是 template specialization(模板特化)MsgSender<CompanyZ>,它们也知道谁人 class(类)没有提供 sendClearMsg 试图挪用的 sendClear function(函数)。
从基础上说,问题就是编译器是早些(当 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(显式基类限定)。