Reading Notes Objective c ++ Item 43 learn how to access the name in the templated base class and define tiveitem
1. Introduction of the problem-the derived class does not find the name in the template base class
Suppose we need to write an application that can send messages to different companies. Messages can be sent in encrypted or plaintext (unencrypted) mode. If we have enough information in the compilation phase to determine which information will be sent to which company, we can use a template-based solution:
1 class CompanyA { 2 public: 3 ... 4 void sendCleartext(const std::string& msg); 5 void sendEncrypted(const std::string& msg); 6 ... 7 }; 8 class CompanyB { 9 public:10 ...11 void sendCleartext(const std::string& msg);12 void sendEncrypted(const std::string& msg);13 ...14 };15 16 ... // classes for other companies17 18 class MsgInfo { ... }; // class for holding information19 // used to create a message20 21 template<typename Company>22 class MsgSender {23 public:24 ... // ctors, dtor, etc.25 26 void sendClear(const MsgInfo& info)27 {28 std::string msg;29 create msg from info;30 Company c;31 c.sendCleartext(msg);32 }33 void sendSecret(const MsgInfo& info) // similar to sendClear, except34 35 36 37 { ... } // calls c.sendEncrypted38 39 }
This works fine, but suppose sometimes we need to log some information before sending the information. A derived class can easily add this information. The following implementation seems reasonable:
1 template<typename Company> 2 3 class LoggingMsgSender: public MsgSender<Company> { 4 5 public: 6 7 8 9 ... // ctors, dtor, etc.10 11 void sendClearMsg(const MsgInfo& info) 12 13 { 14 15 write "before sending" info to the log;16 17 18 sendClear(info); // call base class function;19 // this code will not compile!20 write "after sending" info to the log;21 }22 ...23 };
Note that the message sending function in the derived class is a different name (sendClearMsg) than the basic class (sendClear ). This is a good design, becauseThis avoids hiding inherited names (Item 33), while avoiding the problem of redefining the inherited non-virtual functions(Item 36 ). However, the Code cannot be compiled, and at least the standard compiler cannot. These compilers will complain that sendClear does not exist. We can see that sendClear is in the base class, but the compiler does not find it in the base class. We need to know why.
2. Question Analysis 2.1 General Analysis
The problem occurs when the compiler encounters the definition of the class template LoggingMsgSender, they do not know which class it inherits. Of course, it is inherited from MsgSender <Company>, but Company is a template parameter, which will be confirmed only when LoggingMsgSender is instantiated. Without knowing what Company is, we do not know what MsgSender <Company> looks like. Therefore, there is no way to determine whether the sendClear function exists.
2.2 Use an instance to prove the problem
To make the problem more specific, assume that CompanyZ uses encryption for communication:
1 class CompanyZ { // this class offers no 2 3 public: // sendCleartext function 4 5 ... 6 7 void sendEncrypted(const std::string& msg); 8 9 ... 10 11 };
The general MsgSender template is not suitable for CompanyZ, because the general template provides a function that is meaningless to the CompanyZ object. To correct this problem, we can create a special version of MsgSender for CompanyZ:
1 template<> // a total specialization of2 3 class MsgSender<CompanyZ> { // MsgSender; the same as the4 5 6 public: // general template, except7 ... // sendClear is omitted8 void sendSecret(const MsgInfo& info)9 { ... }
Note the "template <>" syntax at the beginning of the class definition. It indicates that this is neither a template nor a separate class. It is a special version of The MsgSender template that will be used when CompanyZ is used as the template parameter.This is called fully-specific template (Total template specialization): The template MsgSender is of the CompanyZ type and is of the specific nature. Once the type parameter is defined as ComanyZ, the template parameters will not change elsewhere..
When MsgSender has the CompanyZ special version, let's take a look at the derived class LoggingMsgSender:
1 template<typename Company> 2 class LoggingMsgSender: public MsgSender<Company> { 3 public: 4 ... 5 void sendClearMsg(const MsgInfo& info) 6 { 7 write "before sending" info to the log; 8 sendClear(info); // if Company == CompanyZ, 9 // this function doesn’t exist!10 write "after sending" info to the log;11 }12 ...13 };
As mentioned in the annotation, this code does not make sense when the base class is MsgSender <CompanyZ>, because the base class does not provide the sendClear function. This is also the reason why C ++ rejects this call:It realizes that the base class template may be made special, but the special version does not provide the general interface in the general template. Therefore, it does not search for inherited names in the templated base class. In a sense, when we switch from object-oriented C ++ to template C ++ (Item 1), inheritance will stop working.
3. How to Solve the Problem-Three Methods
To make it work again, we must invalidate the action C ++ does not search for in the templated base class. There are three methods to achieve this goal.
First,You can add"This->":
1 template<typename Company> 2 class LoggingMsgSender: public MsgSender<Company> { 3 public: 4 ... 5 void sendClearMsg(const MsgInfo& info) 6 { 7 write "before sending" info to the log; 8 this->sendClear(info); // okay, assumes that 9 // sendClear will be inherited10 write "after sending" info to the log;11 }12 ...13 };
Second,You can useUsingStatement, You may be familiar with it, because Item 33 uses a similar solution. That clause explains how to use the using declaration to bring the hidden base class name to the scope of the derived class. We can implement sendClearMsg in the following way:
1 template<typename Company> 2 class LoggingMsgSender: public MsgSender<Company> { 3 public: 4 using MsgSender<Company>::sendClear; // tell compilers to assume 5 ... // that sendClear is in the 6 // base class 7 void sendClearMsg(const MsgInfo& info) 8 { 9 ...10 11 sendClear(info); // okay, assumes that12 13 ... // sendClear will be inherited14 15 } 16 17 ... 18 19 };
Finally, the Compilation Method for your code isSpecify the function to be called in the base class:
1 template<typename Company> 2 3 class LoggingMsgSender: public MsgSender<Company> { 4 5 public: 6 7 ... 8 9 void sendClearMsg(const MsgInfo& info)10 11 {12 13 ...14 15 MsgSender<Company>::sendClear(info);16 17 // okay, assumes that18 19 20 ... // sendClear will be21 } // inherited22 ...23 };
This is basically the method you are most reluctant to use to solve this problem, because if the called function is virtual, the displayed qualifier will turn off the virtual binding behavior.
From the perspective of name visibility, each method has done the same thing: it promises to the compiler that any subsequent basic class template specialization will support interfaces provided by general templates. This promise is required when all compilers parse a derived class template like LoggingMsgSender, but if this promise is not fulfilled, the truth will emerge in the next compilation. For example, if the following source code has this situation:
1 LoggingMsgSender<CompanyZ> zMsgSender;2 MsgInfo msgData;3 ... // put info in msgData4 5 zMsgSender.sendClearMsg(msgData); // error! won’t compile
Compilation of sendClearMsg will not pass, because from this point, the compiler knows that the base class is the special version of the template MsgSender <CompanyZ>, and they know that this class does not provide the sendClear function that sendClearMsg wants to call.
4. Fundamentals of the discussion in these terms
Basically, this problem is early when the compiler diagnoses invalid references to base class members (when the derived class template is parsed) or late (when these templates are instantiated using specific template parameters. C ++'s policy is to prefer early diagnosis, which is why when the class is special from the template, it assumes that the content of the base class is unknown.
5. Summary
In a derived class template, you can use the prefix "-> this" to reference the base class template name. You can use the using Declaration or the displayed base class qualifier.