news 2026/8/23 8:57:35

Effective C++ 学习笔记 条款43 学习处理模板化基类内的名称

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Effective C++ 学习笔记 条款43 学习处理模板化基类内的名称

假设我们需要编写一个应用程序,可以向多家不同的公司发送消息。消息可以以加密或明文(未加密)的形式发送。如果在编译期间我们有足够的信息来确定哪些消息将发送给哪些公司,那么我们可以采用基于模板的解决方案:


这原本可以正常工作,但假设我们有时希望在每次发送消息时记录一些信息。派生类可以很容易地添加这一功能,而下面这种方式似乎很合理:

注意,派生类中的消息发送函数与基类中的函数名称不同(派生类中叫 sendClearMsg,基类中叫 sendClear)。这是好的设计,因为它绕开了隐藏继承名称的问题(参见条款33),也避免了重新定义继承来的非虚函数所固有的问题(参见条款36)。但上面的代码无法编译,至少对于符合标准的编译器来说是如此。这类编译器会抱怨 sendClear 不存在。我们能看到 sendClear 在基类中,但编译器不会去那里查找。我们需要理解为什么。

问题在于,当编译器遇到类模板 LoggingMsgSender 的定义时,它们不知道它继承自什么类。当然,它是MsgSender<Company>,但 Company 是一个模板参数,要到后来(当 LoggingMsgSender 被实例化时)才能知道。在不知道 Company 是什么的情况下,就没有办法知道MsgSender<Company>这个类长什么样。特别地,也就无法知道它是否有一个 sendClear 函数。

为了让问题具体化,假设我们有一个坚持加密通信的 CompanyZ 类:

通用的 MsgSender 模板不适用于 CompanyZ,因为该模板提供了一个对 CompanyZ 对象毫无意义的 sendClear 函数。为了纠正这个问题,我们可以为 CompanyZ 创建一个 MsgSender 的特化版本:

注意这个类定义开头的template <>语法。它表示这既不是一个模板,也不是一个独立的类。相反,它是 MsgSender 模板的一个特化版本,当模板实参为 CompanyZ 时使用。这被称为全模板特化(total template specialization),即模板 MsgSender 针对类型 CompanyZ 进行了特化,而且这个特化是全的——一旦类型参数被定义为 CompanyZ,模板的其他参数就不可能再有任何变化了。

鉴于 MsgSender 已经针对 CompanyZ 进行了特化,再来看一下派生类 LoggingMsgSender:

正如注释所述,当基类是MsgSender<CompanyZ>时,这段代码毫无意义,因为那个类没有提供 sendClear 函数。这就是 C++ 拒绝该调用的原因:它认识到基类模板可能会被特化,而且这类特化可能不提供与通用模板相同的接口。因此,它通常拒绝在模板化的基类中查找继承来的名称。从某种意义上说,当我们从面向对象的 C++ 跨越到模板 C++(参见条款1)时,继承就失效了。

要重新启用它,我们必须以某种方式禁用 C++ 的“不要在模板化基类中查找”行为。有三种方法可以做到这一点。第一种,你可以在调用基类函数时加上this->前缀:

第二种方法是使用 using 声明。如果你读过条款33,这个解决方案应该会让你感到熟悉。条款33解释了 using 声明如何将隐藏的基类名称引入派生类的作用域。因此我们可以这样写 sendClearMsg:

虽然 using 声明在这里和条款33中都能用,但所解决的问题是不同的。这里的情况并不是基类名称被派生类名称隐藏,而是编译器不会去搜索基类作用域,除非我们告诉它们这样做。

第三种让代码编译的方法,是显式指定被调用的函数在基类中:

这种做法通常是最不可取的方式,因为如果被调用的函数是虚函数,显式限定会关闭虚函数的动态绑定行为。

从名称可见性的角度来看,这三种做法效果相同:它们都向编译器承诺,基类模板的任何后续特化都会支持通用模板所提供的接口。当编译器解析像 LoggingMsgSender 这样的派生类模板时,它们只需要这个承诺就足够了。但如果这个承诺最终不成立,真相会在后续编译中暴露出来。例如,如果源码后面出现这样一段:

对 sendClearMsg 的调用将无法编译,因为在此时,编译器已经知道基类是模板特化MsgSender<CompanyZ>,并且知道该类没有提供 sendClearMsg 试图调用的 sendClear 函数。

从根本上说,问题在于编译器是更早地诊断出对基类成员的错误引用(在解析派生类模板定义时),还是更晚(在用特定模板实参实例化这些模板时)。C++ 的策略是倾向于早期诊断,这就是为什么它假定自己对从模板实例化出的基类内容一无所知。

切记:
1.在派生类模板中,通过 this-> 前缀、using 声明或显式基类限定来引用基类模板中的名称。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/23 8:55:45

ACM模式训练系统:从解题到工程化交付的实战指南

1. 这不是刷题网站&#xff0c;而是一套可落地的ACM模式训练系统“如何练习笔试中的ACM模式&#xff1f;这个网站上线了&#xff01;”——看到标题时&#xff0c;我第一反应不是点开链接&#xff0c;而是把手机倒扣在桌面上&#xff0c;泡了杯浓茶。干这行十多年&#xff0c;带…

作者头像 李华
网站建设 2026/8/23 8:53:59

Linux PipeWire深度解析之pw_context_connect调用流程与实战(七十七)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》作者 博主新书推荐&#xff1a;《Android系统多媒体进阶实战》&#x1f680; Android Audio工程师专栏地址&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; Android多媒体专栏地址&a…

作者头像 李华
网站建设 2026/8/23 8:52:36

深入解析JavaScript原型链继承:从原理到ES6 Class的底层实现

1. 从“对象”到“继承”&#xff1a;为什么我们需要原型链&#xff1f; 如果你写过一段时间的JavaScript&#xff0c;尤其是从其他语言&#xff08;比如Java、C#&#xff09;转过来的&#xff0c;大概率会对JS的“面向对象”感到困惑。我们明明可以用 function 定义一个“类…

作者头像 李华
网站建设 2026/8/23 8:51:30

【MATLAB例程,车联网16】基于V2X通信的干线绿波速度引导控制仿真——多交叉口信号相位信息驱动的车速动态优化,对比无引导的停车次数、总延误、时距轨迹及交叉口通过时间。附下载链接

包运行成功&#xff0c;原创代码&#xff0c;欢迎讨论研究&#xff0c;但禁止翻卖 文章目录程序简介概述场景建模对比方法运行结果MATLAB源代码程序简介 概述 本例程面向城市干线多交叉口绿波速度引导问题&#xff0c;利用多个交叉口的信号相位与相位差信息&#xff0c;为车辆…

作者头像 李华
网站建设 2026/8/23 8:49:07

Lucas定理优化实现:大组合数模小质数的高效计算

1. 项目概述&#xff1a;当组合数遇上大质数 在算法竞赛和数论编程中&#xff0c;计算组合数 C(n, m) 是一个经典且高频的需求。当 n 和 m 的数值在常规范围内&#xff08;比如 10^5 以内&#xff09;&#xff0c;我们可以轻松地通过预处理阶乘和逆元&#xff0c;利用公式 C(n,…

作者头像 李华