• Effective C++ 笔记 —— Item 43: Know how to access names in templatized base classes.


    Suppose we need to write an application that can send messages to several different companies. Messages can be sent in either encrypted or cleartext (unencrypted) form. If we have enough information during compilation to determine which messages will go to which companies, we can employ a template-based solution:

    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 for holding information used to create a message
    class MsgInfo { /*...*/ }; 
    
    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
            //...
        }
    };
    
    template<typename Company>
    class LoggingMsgSender : public MsgSender<Company> {
    public:
        // ...
        void sendClearMsg(const MsgInfo& info)
        {
            // write "before sending" info to the log;
    
            this->sendClear(info);     // call base class function this code will not compile!
    
            // write "after sending" info to the log;
        }
        //...
    };

    The code above won't compile, at least not with conformant compilers. Such compilers will complain that sendClear doesn't exist. We can see that sendClear is in the base class, but compilers won't look for it there. We need to understand why.

    The problem is that when compilers encounter the definition for the class template LoggingMsgSender, they don't know what class it inherits from. Sure, it's MsgSender<Company>, but Company is a template parameter, one that won't be known until later (when LoggingMsg Sender is instantiated). Without knowing what Company is, there's no way to know what the class MsgSender<Company> looks like. In particular, there's no way to know if it has a sendClear function

    To make the problem concrete, suppose we have a class CompanyZ that insists on encrypted communications, we create a specialized version of MsgSender for CompanyZ:

    // this class offers no sendCleartext function
    class CompanyZ
    {
    public:
        // ...
        void sendEncrypted(const std::string& msg);
        // ...
    };
    
    
    // a total specialization of MsgSender; the same as the general template, except sendClear is omitted
    template<> 
    class MsgSender<CompanyZ> 
    {
    public:  
        // ...  
        void sendSecret(const MsgInfo& info)
        {
            // ...
        }
    };

    Given that MsgSender has been specialized for CompanyZ, consider again the 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;
        }
        //...
    };

    As the comment notes, this code makes no sense when the base class is MsgSender, because that class offers no sendClear function. That's why C++ rejects the call: it recognizes that base class templates may be specialized and that such specializations may not offer the same interface as the general template. As a result, it generally refuses to look in templatized base classes for inherited names.

    To restart it, we have to somehow disable C++'s "don't look in templatized base classes" behavior. There are three ways to do this.

    • First, you can preface calls to base class functions with "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;
        }
        //...
    };
    • Second, you can employ a using declaration
    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)
        {
            // write "before sending" info to the log;
    
            sendClear(info);     // okay, assumes that sendClear will be inherited
    
            // write "after sending" info to the log;
        }
        //...
    };
    • A final way to get your code to compile is to explicitly specify that the function being called is in the 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
            // ... 
        }
    };

    This is generally the least desirable way to solve the problem, because if the function being called is virtual, explicit qualification turns off the virtual binding behavior.

    From a name visibility point of view, each of these approaches does the same thing: it promises compilers that any subsequent specializations of the base class template will support the interface offered by the general template. Such a promise is all compilers need when they parse a derived class template like LoggingMsgSender, but if the promise turns out to be unfounded, the truth will emerge during subsequent compilation. For example, if the source code later contains this:

    LoggingMsgSender<CompanyZ> zMsgSender;
    MsgInfo msgData;
    // ... // put info in msgData
    zMsgSender.sendClearMsg(msgData); // error! won't compile

    the call to sendClearMsg won't compile, because at this point, compilers know that the base class is the template specialization MsgSender, and they know that class doesn't offer the sendClear function that sendClearMsg is trying to call.

    Things to Remember

    • In derived class templates, refer to names in base class templates via a "this->" prefix, via using declarations, or via an explicit base class qualification.
  • 相关阅读:
    Javascript高级编程学习笔记(32)—— 客户端检测(1)能力检测
    Javascript高级编程学习笔记(31)—— BOM(5)screen、history对象
    Javascript高级编程学习笔记(30)—— BOM(4)navigator对象
    Javascript高级编程学习笔记(29)—— BOM(3)location对象
    Javascript高级编程学习笔记(28)—— BOM(2)window对象2
    Javascript高级编程学习笔记(27)—— BOM(1)window对象1
    逆向与反汇编工具
    Silicon Labs
    sk_buff 里的len, data_len, skb_headlen
    IP分片重组的分析和常见碎片攻击 v0.2
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15673330.html
Copyright © 2020-2023  润新知