Effective C++条款43:模板与泛型编程之(学习处理模板化基类内的名称)

一、模板中,派生类不可调用模板基类的成员函数

  • 在模板类中,如果一个派生类在其方法中调用了基类的方法,那么这段代码可能无法编译通过
  • 备注(重点):
    • 这一现象是与编译器有关的,Effective C++的作者编译的时候出错
    • 本人使用VS编译的时候没有出错
    • 因此这个问题是与编译器有关

演示说明

  • 假设现在有这样一个类体系:
    • 我们有若干公司类,其包含两个成员函数可以用来将信息发送到公司(一个为发送加密信息,一个为发送不加密信息)
    • 有一个MsgSender类,其中有两个成员函数,函数中可以定义若干公司,然后调用公司的成员方法向公司发送信息
    • 有一个MsgInfo类,用来封装消息(这个类不重要)
  • 代码如下:
//公司类
class CompanyA {
public:
    void sendCleartext(const std::string& msg); //向公司发送未加密信息
    void sendEncrypted(const std::string& msg); //向公司发送加密信息
};

class MsgInfo {}; //封装信息的类

//发送信息类
template<typename Company>
class MsgSender {
public:
    //在其中定义公司A,并调用公司A的sendCleartext()函数向公司A发送信息
    void sendClear(const MsgInfo& info)
    {
        std::string msg;
        CompanyA a;
        a.sendCleartext(msg);
    }

    //同上,只是发送加密信息
    void sendSecret(const MsgInfo& info)
    {
        std::string msg;
        CompanyA a;
        a.sendEncrypted(msg);
    }
};
  • 现在我们为MsgSender类添加了一个派生类,我们希望每次在发送信息的时候记录一下日志。因此定义如下:
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        //记录一下日志
        sendClear(info); //调用基类的方法发送消息,这段代码可能无法编译通过
        //记录一下日志
    }

    void sendSecretMsg(const MsgInfo& info)
    {    
        //记录一下日志
        sendSecret(msg); //调用基类的方法发送消息,这段代码可能无法编译通过
        //记录一下日志
    }
};
  • 上面代码对于某些编译器会出错的原因在于:
    • 编译期出错:当类遇到LoggingMsgSender类模板定义式时,其并不知道LoggingMsgSender继承的class属于什么类型,因为还没有具体被实例化
    • 因此在编译到LoggingMsgSender的成员函数时,其并不知道其基类是否有一个sendClear()函数

针对于错误的原因,我们再做一个演示案例

  • 上面介绍了,在编译派生类调用基类成员函数的时候编译器出错。现在我们再看一个演示案例,能够更加让你理解这种错误的原因
  • 例如:
    • 现在我们有一个公司Z,其接收的消息只支持加密方式,因此,公司Z中只定义sendEncrypted()函数
    • 因为公司Z只发送加密消息,所以对于前面定义的MsgSender类模板就不适合于我们的公司Z了,因为公司Z不需要发送普通的消息。因此我们需要针对于公司Z全特化一个MsgSender类模板
    • 代码如下:
//公司Z,只接收加密消息
class CompanyZ {
public:
    void sendEncrypted(const std::string& msg);
};

//针对于公司Z的全特化版本,发送加密消息
template<>
class MsgSender<CompanyZ> {
public:
    void sendSecret(const MsgInfo& info)
    {
        std::string msg;
        CompanyZ z;
        z.sendEncrypted(msg);
    }
};
  • 上面针对于MsgSender进行了全特化,现在我们再来看看上面的LoggingMsgSender类模板为什么会报错:
    • 如果LoggingMsgSender的基类MsgSender的类型为CompanyZ时,那么下面的代码就是错误的了
    •  因为CompanyZ不拥有sendClear函数
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
    //如果Company的类型为CompanyZ,,那么此处会出错,因为CompanyZ不拥有sendClear函数
    void sendClearMsg(const MsgInfo& info)
    {
        sendClear(info);
    }
    void sendSecretMsg(const MsgInfo& info)
    {
        sendSecret(info);
    }
};
  • 错误的原因总结:
    • 我们在编写非模板类的时候,在派生类中调用某函数时,如果在本类中没有查找到该函数,那么就会向基类的作用域中去查找函数
    • 但是模板类不一样,在派生类中调用某函数时,如果在本类中没有查找到该函数,那么其不会继续向基类中进行查找

二、解决上面错误的3种方法

第一种方法

  • 使用this指针:使用this指针调用这些函数,实现先告诉编译器这些函数是属于自身类的(在编译之后它们会从基类中继承而来)
  • 例如:
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        //记录一下日志
        this->sendClear(info);
        //记录一下日志
    }
    void sendSecretMsg(const MsgInfo& info)
    {    
        //记录一下日志
        this->sendSecret(info);
        //记录一下日志
    }
};

第二种方法

  • 使用using声明式

  • 注意这种using声明式与非模板类的不同:

    • 在非模板类中,使用using是为了防止派生类隐藏继承的方法,而使基类中的方法在派生类中可见

    • 在模板类中,使用using是为了让编译器去基类中查找这个函数

  • 例如:

template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
    using MsgSender<Company>::sendClearMsg;
    using MsgSender<Company>::sendSecretMsg;

    void sendClearMsg(const MsgInfo& info)
    {
        //记录一下日志
        sendClear(info);
        //记录一下日志
    }
    void sendSecretMsg(const MsgInfo& info)
    {    
        //记录一下日志
        sendSecret(info);
        //记录一下日志
    }
};

第三种方法

  • 明确指出被调用的函数位于base class中
  • 这种方法不太建议,因为:被调用的函数可能是virtual函数,这种修饰符会关闭“virtual绑定行为”
  • 例如:
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        //记录一下日志
        sgSender<Company>::sendClear(info);
        //记录一下日志
    }
    void sendSecretMsg(const MsgInfo& info)
    {    
        //记录一下日志
        sgSender<Company>::sendSecret(info);
        //记录一下日志
    }
};
  • 总结:
    • 上面介绍了三种方法都是可以解决让模板类编译通过的方法
    • 对于全特化版本来说也可以编译通过,直到调用不存在的方法时才会报错。例如对上面的CompanyZ的全特化MsgSender版本调用sendClearMsg函数时就会报错。例如:
class CompanyZ { };
class MsgInfo {};
template<typename Company>
class MsgSender { };

//全特化版本
template<>
class MsgSender<CompanyZ> {
public:
    void sendSecret(const MsgInfo& info)
    {
        std::string msg;
        CompanyZ z;
        z.sendEncrypted(msg);
    }
};

template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info)
    {
        this->sendClear(info);
    }
    void sendSecretMsg(const MsgInfo& info)
    {
        this->sendSecret(info);
    }
};

int main()
{
    //此段代码仍然可以编译通过,即使CompanyZ不支持sendClearMsg
    LoggingMsgSender<CompanyZ> zMsgSender;
    
    MsgInfo msgData;
    zMsgSender.sendSecretMsg(msgData);
    //zMsgSender.sendClearMsg(msgData); //不能调用这一句,否则报错
	return 0;
}

三、总结

  • 可在derived class templates内通过“this”指涉base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成
发布了1525 篇原创文章 · 获赞 1084 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104850617