C ++ proverbs: how to access the names of templated base classes

Source: Internet
Author: User

Suppose we want to write an application that can send messages to several different companies. Messages can be transmitted in either encrypted or plaintext (not encrypted) mode. If we have enough information to determine which message will be sent to which company during compilation, we can use a template-based solution to solve the problem:

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
};

This works well, but suppose we sometimes need to record some information into the log every time a message is sent. Using a derived class (derived class), you can simply add this function. The following seems a reasonable method:

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;
 }
 ...
};

Note that the name (sendClearMsg) of the message-sending function (message sending function) in the derived class (derived class) corresponds to the one (where, it is called sendClear. This is a good design because it avoids the problem of hiding inherited names (hiding inherited names) (see 《C ++ proverbs: Avoid overwriting the names obtained through inheritance.) And redefine the inherent problems of an inherited non-virtual function (inherited non-virtual function) (see 《C ++ proverbs: Do not redefine the inherited non-virtual functions). However, the above Code cannot be compiled, at least in a compliant compiler. Such a compiler will complain that sendClear does not exist. We can see that sendClear is in the base class (base class), but the compiler won't look for it there. We need to understand why.

The problem is that when the compiler encounters the class template (class template) LoggingMsgSender's definition (definition), they do not know which class it inherits from. Of course, it is MsgSender But Company is a template parameter (template parameter), which can only be determined later (when LoggingMsgSender is instantiated ). If you don't know what Company is, you can't know the class (class) MsgSender. What is it like. In particular, there is no way to know whether it has a sendClear function (function ).

To make the problem more specific, suppose we have a class (class) CompanyZ that requires encrypted communication:

class CompanyZ { // this class offers no
 public: // sendCleartext function
 ...
 void sendEncrypted(const std::string& msg);
 ...
};

Generally, MsgSender template (template) is not applicable to CompanyZ, because the template provides a sendClear function (function) that is meaningless to CompanyZ objects (object. To solve this problem, we can create a special version of MsgSender for CompanyZ:

template<>// a total specialization of
class MsgSender { // MsgSender; the same as the
public: // general template, except
... // sendCleartext is omitted
void sendSecret(const MsgInfo& info)
{ ... }
};

Note the "template <>" syntax at the beginning of this class definition (class definition. It indicates that this is neither a template nor standalone class ). The correct statement is that it is a specialized version (special version) used for the MsgSender template (template parameter) when the template argument (template parameter) is CompanyZ ). This is famous for its total template specialization (full template specialization): template (template) MsgSender is specially tailored to the CompanyZ type, and this specialization (specialization) is total (complete) -- as long as the type parameter (type parameter) is defined as CompanyZ, there will be no other templates parameters (Template parameter) that can be changed ).

MsgSender is known to be specially crafted for CompanyZ. Next, consider derived class (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 doesnt exist!
  write "after sending" info to the log;
 }
 ...
};

As written in the annotation, when the base class (base class) is MsgSender The code here is meaningless because the class does not provide the sendClear function ). This is why C ++ rejects this call: it recognizes that the base class templates (base class template) can be made special, and this feature is not necessarily provided with the general template (general template) the same interface ). As a result, it usually rejects the search for inherited names (inherited names) in the templatized base classes (templated base class ). In a sense, when we span from Object-oriented C ++ to Template C ++, inheritance will stop working.

To restart it, we must invalidate the "dont look in templatized base classes" (not found in the template base class) of C ++ in some way. There are three ways to do this. First, you can add "this->" before calling base class functions (base class function ":

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;
}
...
};

Second, you can use a using declaration. If you have read 《 C ++ proverbs: Avoid overwriting the names obtained through inheritance.It should be a solution that you are familiar. This article explains how using declarations can introduce hidden base class names (base class name) into a derived class (derived class) field. Therefore, we can write sendClearMsg as follows:

template
class LoggingMsgSender: public MsgSender {
public:
 using MsgSender ::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
 }
 ...
};

(Although the using declaration and 《 C ++ proverbs: Avoid overwriting the names obtained through inheritance.But the problems to be solved are different. In this case, the base class names (the base class name) is not hidden by the derived class names (the name of the derived class), but the compiler will not search for the base class field if we do not tell it to do so .)

The last way to compile your code is to explicitly specify that the called function is in the base class (base class:

template
class LoggingMsgSender: public MsgSender {
public:
...
void sendClearMsg(const MsgInfo& info)
{
 ...
 MsgSender ::sendClear(info); // okay, assumes that
 ... // sendClear will be
} // inherited

...
};

This is usually the most undesirable method to solve this problem, because if the called function is virtual, the virtual binding will be disabled explicitly.

From the perspective of name visibility, every method has done the same thing: it guarantees the compiler the specializations (special) of any subsequent base class template (base class template) interfaces provided by general templates are supported ). All compilers are parsing a derived class template (derived class template) Like LoggingMsgSender. Such a guarantee is necessary, but if it is confirmed that it is not true, the truth will be exposed during subsequent compilation. For example, if the following source code contains,

LoggingMsgSender zMsgSender;
MsgInfo msgData;
... // put info in msgData
zMsgSender.sendClearMsg(msgData); // error! wont compile

Calls to sendClearMsg cannot be compiled, because at this moment, the compiler knows that the base class (base class) is template specialization (template-specific) MsgSender. They also know

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.