effective c++ 笔记 条款41-46

条款41:了解隐式接口和编译器多态

显式接口和运行期多态: 面向对象的世界总是以显式接口和运行期多态解决问题
显式接口的构成: 函数名称,参数类型,返回类型,常量性也包括编译器产生的copy构造函数和copy assignment 操作符。( 函数的签名式 )
多态是通过虚函数发生于运行期

class Widget {
public:virtual void Normalize()=0;
};
class AWidget:public Widget {
public:virtual void Normalize() override {...}
};
class BWidget:public Widget {
public:virtual void Normalize() override {...}
};
void doProcessing(Widget& w) {w.Normalize();
}

由于doProcessing的w被声明为Widget,所以w必须支持Widget接口,可以在源码中找到接口,实际可见,是显示接口。
隐式接口和编译期多态: templates及泛型编程的世界,与面向对象的世界有根本的不同。在此世界显式接口和运行期多态仍然存在,但重要性降低
隐式接口的构成: 有效表达式
多态是通过 template 具现化和函数重载解析(function overloading resolution)发生于编译期

template<typename T>
void doProcessing(T& w) {if (w.size() > 10 && w != someNastyWidget) {...

对w而言,必须支持什么接口,由doProcessing中的操作决定。上述例子中需要支持
提供一个名为size的成员函数,该函数的返回值可与int(10 的类型)执行operator>,或经过隐式转换后可执行operator>。
必须支持一个operator!=函数,接受T类型和someNastyWidget的类型,或其隐式转换后得到的类型。

条款42:了解typename的双重意义

声明template参数时,前缀关键字class和typename可互换
模板内出现的名称如果相依于某个模板参数,我们称之为从属名称(dependent names);如果从属名称在类内呈嵌套状,我们称之为嵌套从属名称(nested dependent name);如果一个名称并不倚赖任何模板参数的名称,我们称之为非从属名称(non-dependent names)
使用关键字typename标识嵌套从属类型名称
但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符

template<typename T>
void print(const T& container)
{T::const_iterator* m;...
}

如果T::const_iterator它不是一个类型,而是T中的一个静态成员变量,而这时m碰巧也是一个全局变量。那么就不是定义一个指针,而是两个静态变量相乘。
因此C++ 默认假设嵌套从属名称不是类型名称
因此下面这段代码会编译报错

template<typename C>
void Print2nd(const C& container) {if (container.size() >= 2) {C::const_iterator iter(container.begin());}
}

因为C::const_iterator是一个指向某类型的嵌套从属类型名称,可能导致解析困难,因为在编译器知道C是什么之前,没有任何办法知道C::const_iterator是否为一个类型
需要显示指明

typename C::const_iterator iter(container.begin());

typename只被用来验明嵌套从属类型名称

template<typename C>                 //允许使用"typename"(或"class")
void f(const C&container,           //不允许使用"typename"
typename C::iterator iter)//一定要使用"typename"
template<typename T>
class Derived : public Base<T>::Nested {    // 基类列表中不允许使用 typename
public:explicit Derived(int x): Base<T>::Nested(x) {                 // 成员初始化列表中不允许使用 typenametypename Base<T>::Nested temp;...}...
};

当类型名过于复杂,可以使用using或typedef来进行简化

using value_type = typename std::iterator_traits<IterT>::value_type;

条款43:学习处理模板化基类内的名称

class MsgInfo { ... };
template<typename Company>
class MsgSender {
public:void SendClear(const MsgInfo& info) { ... }...
};
template<typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:void SendClearMsg(const MsgInfo& info) {SendClear(info);        // 调用基类函数,这段代码无法通过编译,SendClear找不到标识符}...
};

如果父类是模板类,那么子类直接调用父类的成员函数无法通过编译器,因为可能会有特化版的模板类针对某个类不声明该接口函数,编译器并不知道究竟继承哪个类( T 是一个 template 参数,不到后来的具现化,是无法确切知道它是什么),即无法知道是否有SendClear函数。
如特化版 class Company (模板全特化)
template<>为特化模板标志

template<>
class MsgSender <CompanyZ>{
public://void SendClear(const MsgInfo& info) { ... }...
};

三种解决办法

  1. 在基类函数调用动作之前加上this->:
this->SendClear(info);  告诉编译器,假设SendClear被继承
  1. 使用 using 声明式。告诉编译器进入 base class 作用域寻找函数
using MsgSender<Company>::SendClear;
SendClear(info);

条款33不同,这里并不是base class名称被derived class名称遮掩,而是编译器不进入base class作用域内查找,于是我们通过using告诉它,请它那么做

  1. 明确指出被调用函数位于 base class 内
MsgSender<Company>::SendClear(info);

不推荐,因为如果调用的是虚函数,明确资格修饰(explicit qualification)会使“虚函数绑定行为”失效

条款44:将与参数无关的代码抽离

template class 成员依赖 template 参数值,会造成造成代码膨胀,即non-type template parameters(非类型模板参数)带来的膨胀

template <typename T, std::size_t n>
class SquareMatrix {public:void invert();private:std::array<T, n * n> data;
};
SquareMatrix<double, 5> m1;
SquareMatrix<double, 10> m2;

会具有两份几乎一样的代码,除了一个参数5,一个参数10
改进

template<typename T>
class SquareMatrixBase {
protected:void Invert(std::size_t matrixSize);...
private:std::array<T, n * n> data;
};template<typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T> {  // private 继承实现,见条款 39using SquareMatrixBase<T>::Invert;              // 避免掩盖基类函数,见条款 33
public:void Invert() { this->Invert(n); }              // 调用模板基类函数,见条款 43...
};

可能不止invert函数会使用矩阵操作,每次传尺寸过于繁琐,可以考虑将数据放在子类中,在父类存储指针和矩阵尺寸

template<typename T>
class SquareMatrixBase {
protected:SquareMatrixBase(std::size_t n, T* pMem): size(n), pData(pMem) {}void SetDataPtr(T* ptr) { pData = ptr; }...
private:std::size_t size;T* pData;
};template<typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
public:SquareMatrix() : SquareMatrixBase<T>(n, data.data()) {}...
private:std::array<T, n * n> data;
};

type parameters (类型参数)的膨胀:
很多平台上,int和long有相同的二进制表述,所以vector< int>和vector< long>的成员函数可能完全相同。
多数平台上,所有指针类型都有相同的二进制表述,因此凡模板持有指针者(比如list< int>、list< const int >等)往往应该对每一个成员使用唯一一份底层实现。
也就是说,如果你实现成员函数而它们操作强类型指针(T),你应该令它们调用另一个无类型指针(void )的函数,由后者完成实际工作。

  1. Templates 生成多个 classes 和多个 functions,所以任何 template 代码都不该与某个造成膨胀的 template 参数产生相依关系。
  2. 因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可以消除,做法是以函数参数或 class 成员变量替换 template 参数。
  3. 因类型参数(type parameters)而造成的代码膨胀,往往可以降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码。

条款45:运用成员函数模板接受所有兼容类型

原始指针支持隐式转换,如子类指针可以隐式转化为父类指针,非const指针可以转为const指针。
对于template具现的类,无法像原始指针一样隐式转化。因为不存在子类-父类一样的关系。是完全不同的类。唯一的方式是明确编写模板拷贝构造函数

template<typename T>
class SmartPtr {
public:template<typename U>SmartPtr(const SmartPtr<U>& other): heldPtr(other.get()) { ... }T* get() const { return heldPtr; }...
private:T* heldPtr;
};

使用get获取原始指针,用原始指针进行类型转换,如果原始指针之间不能隐式转换,那么其对应的智能指针之间的隐式转换会造成编译错误。
模板构造函数并不会阻止编译器暗自生成默认的构造函数,所以如果你想要控制拷贝构造的方方面面,你必须同时声明泛化拷贝构造函数和普通拷贝构造函数,相同规则也适用于赋值运算符

template<typename T>
class shared_ptr {
public:shared_ptr(shared_ptr const& r);                // 拷贝构造函数template<typename Y>shared_ptr(shared_ptr<Y> const& r);             // 泛化拷贝构造函数shared_ptr& operator=(shared_ptr const& r);     // 拷贝赋值运算符template<typename Y>shared_ptr& operator=(shared_ptr<Y> const& r);  // 泛化拷贝赋值运算符
};

条款46:需要类型转换时请为模板定义非成员函数

条款24讨论过为什么唯有non-member函数才有能力“在所有实参身上实施隐式类型转换”
模版类中的模板函数不支持隐式类型转换,如果在调用时传了一个其他类型的变量,编译器无法帮你做类型转换,从而报错

template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) {return Rational<T>(lhs.Numerator() * rhs.Numerator(), lhs.Denominator() * rhs.Denominator());
}
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2;     // 无法通过编译!

以oneHalf推导出Rational类型可行,但是将int类型隐式转换为Rational是会失败。2是一个int,编译器无法从int推算出Rational的T是什么类型
由于模板类不依赖模板实参推导,所以编译器能够在Rational具现化时得知T,因此可以使用友元声明式在模板类内指涉特定函数

template<typename T>
class Rational {
public:friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};

当对象oneHalf被声明为一个Rational时,Rational类也被具现化出来,友元函数operator*也被声明出来,作为一个普通函数,可以在接受参数时隐时转换,但是此时类外部并没有实际定义,类外函数模版仍不知道,会链接失败
因此直接将定义合并至声明处

friend const Rational operator*(const Rational& lhs, const Rational& rhs) {return Rational(lhs.Numerator() * rhs.Numerator(), lhs.Denominator() * rhs.Denominator());
}

类内的函数会暗自成为内联函数,因此可以使operator*调用类外的辅助模板函数:

template<typename T> class Rational;template<typename T>
const Rational<T> DoMultiply(const Rational<T>& lhs, const Rational<T>& rhs) {return Rational<T>(lhs.Numerator() * rhs.Numerator(), lhs.Denominator() * rhs.Denominator());
}
template<typename T>
class Rational {
public:...friend const Rational operator*(const Rational& lhs, const Rational& rhs) {return DoMultiply(lhs, rhs);}...
};

当我们编写一个 class template,而它所提供之与此 template 相关的函数支持所有参数隐式类型转换时,请将那些函数定义为 class template 内部的 friend 函数。

条款47:请使用traits classes表现类型信息

条款48:认识 template 元编程

没看懂,之后去研究下traits和模版

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/728542.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

142.环形链表II

142.环形链表II 力扣题目链接(opens new window) 题意&#xff1a; 给定一个链表&#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 为了表示给定链表中的环&#xff0c;使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索引从 0…

Linux性能分析之CPU实战

本课程专注于教授学员如何利用各种工具和技术来分析Linux系统中的CPU性能问题。通过实际操作和案例研究&#xff0c;学员将深入了解CPU性能优化和故障排除&#xff0c;提升其在Linux环境下的技能水平。 课程大小&#xff1a;1.9G 课程下载&#xff1a;https://download.csdn.…

例行性工作(at,crontab)

目录 单一执行的例行性工作at 语法 选项 时间格式 at的工作文件存放目录 at工作的日志文件 实例 命令总结&#xff1a; 循环执行的例行性工作crond 语法 选项 crontab工作调度对应的系统服务 crontab工作的日志文件 用户定义计划任务的文件所在目录 动态查看 crontab文件格式 文…

js拓展-内置对象

目录 1. 数组对象 1.1 数组的四种方式 1.2 JS中数组的特点 1.3 常用方法 2. 日期对象 2.1 日期对象的创建 2.2 日期对象的方法 2.3 案例&#xff1a;输出现在的时间 3. 全局对象 3.1 字符串转换成数字类型 3.2 编码解码函数 1. 数组对象 注&#xff1a;数组在JS中是一…

企业如何进行数据分析,实现科学决策和业务增长

在当今信息时代&#xff0c;数据已经成为企业运营和发展的重要资源。企业拥有大量的数据&#xff0c;包括但不限于销售数据、客户数据、市场数据、企业内部管理数据等等&#xff0c;这些数据记录了企业的运营状况和业务发展情况。但是&#xff0c;这些数据如果不进行分析和利用…

尤雨溪:Vue 未来展望新的一轮

十年&#xff0c;一个既漫长又短暂的时光跨度&#xff0c;对于技术世界来说&#xff0c;更是沧海桑田的瞬间。在这十年里&#xff0c;Vue.js 从无到有&#xff0c;从默默无闻到蜚声全球&#xff0c;不仅改变了前端开发的面貌&#xff0c;更成为了无数开发者手中的得力工具。 在…

软件开发未来发展方向

传统的软件开发行业未来的趋势将会是更加自动化和智能化。随着人工智能和机器学习技术的发展&#xff0c;软件开发过程中的一些重复性、繁琐的工作将会被自动化取代&#xff0c;从而提高开发效率和质量。同时&#xff0c;随着物联网、大数据和云计算等技术的成熟&#xff0c;软…

react项目请求无法自动携带上一次请求的JSESSIONID

存在问题 页面依次发起多次请求&#xff0c;服务端会根据有无携带Set-Cookie从而生成新的JSESSIONID返回&#xff0c;前端需要在后续请求自动携带该值&#xff08;浏览器行为&#xff0c;无需代码控制&#xff09; 但目前的情况为&#xff1a;前端请求无法自动携带&#xff0c;…

建图以及DFS、BFS模板

(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨ &#x1f440;&#x1f440;&#x1f440; 个人博客&#xff1a;小奥的博客 &#x1f44d;&#x1f44d;&#x1f44d;&#xff1a;个人CSDN ⭐️⭐️⭐️&#xff1a;传送门 &#x1f379; 本人24应届生一枚&#xff0c;技术和水平有限&am…

蓝桥杯---列名

题目链接&#xff1a;列名 直接模拟出来了 import java.math.BigInteger; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; import java.util.Scanner;public class Main {public static void main(String[] args) {StringBuilder snew String…

高效实用|ChatGPT指令/提示词/prompt/AI指令大全,基础版

大家好&#xff0c;我是淘小白~ 整理了一些关于chatpgt的指令文档分享给大家~ 如果对你有用记得点赞、关注、收藏哦~ 基础版指令主要用于简单任务和场景&#xff0c;英语翻译&#xff0c;发散问答&#xff0c;文章故事写作&#xff0c;周报生成等&#xff0c;在使用过程中&a…

Pytest教程:详解Pytest的三种多级断言方法

当涉及到测试代码时&#xff0c;多级断言可以提供更全面的测试覆盖&#xff0c;并且允许开发者一次性检查多个方面的代码行为。在 Pytest 中&#xff0c;有三种主要的方式来实现多级断言&#xff1a;使用多个普通的 assert 语句、使用 pytest-assume 插件以及使用 pytest-check…

【RT-DETR有效改进】全新的SOATA轻量化下采样操作ADown(轻量又涨点,附手撕结构图)

一、本文介绍 本文给大家带来的改进机制是利用2024/02/21号最新发布的YOLOv9其中提出的ADown模块来改进我们的Conv模块,其中YOLOv9针对于这个模块并没有介绍,只是在其项目文件中用到了,我将其整理出来用于我们的RT-DETR的项目,经过实验我发现该卷积模块(作为下采样模块)…

Node:解决Error: error:0308010C:digital envelope routines::unsupported的四种解决方案

问题描述&#xff1a; 报错&#xff1a;Error: error:0308010C:digital envelope routines::unsupported 报错原因&#xff1a; 主要是因为 nodeJs V17 版本发布了 OpenSSL3.0 对算法和秘钥大小增加了更为严格的限制&#xff0c;nodeJs v17 之前版本没影响&#xff0c…

多路归并总结

1.鱼塘钓鱼 1262. 鱼塘钓鱼 - AcWing题库 多路归并的模型。 对于每个鱼塘构成的等差数列&#xff0c;我们每次在数列最头部进行选择&#xff0c;选完后再顺延到下一个数即可。我们可以通过维护一个包含所有等差序列首元素的大根堆&#xff0c;使每次可以很容易地选出最大的数…

Vivado使用记录(未完待续)

一、Zynq开发流程 二、软件安装 三、软件使用 字体大小修改&#xff1a;Setting、Font 四、Vivado基本开发流程 1、创建工程 Quick Start 组包含有 Create Project&#xff08;创建工程&#xff09;、 Open Project&#xff08;打开工程&#xff09;、 Open Example Project&…

List之ArrayList、LinkedList深入分析

集合 Java 集合&#xff0c; 也叫作容器&#xff0c;主要是由两大接口派生而来&#xff1a;一个是 Collection接口&#xff0c;主要用于存放单一元素&#xff1b;另一个是 Map 接口&#xff0c;主要用于存放键值对。对于Collection 接口&#xff0c;下面又有三个主要的子接口&…

《剑指 Offer》专项突破版 - 面试题 75 : 数组相对排序(C++ 实现)

目录 计数排序详解 面试题 75 : 数组相对排序 计数排序详解 计数排序是一种线性时间的整数排序算法&#xff0c;其算法步骤为&#xff1a; 找出待排序数组 nums 中的最小值和最大值&#xff08;分别用 min 和 max 表示&#xff09;。 创建一个长度为 max - min 1、元素初始…

洗衣洗鞋店小程序对接水洗唛打印,一键预约,支付无忧

随着社会的进步和科技的发展&#xff0c;我们的生活幸福感与日俱增。为了让我们从琐碎中解脱出来&#xff0c;干洗店洗鞋店行业也日新月异。今天&#xff0c;我为大家推荐这款优秀的干洗店小程序系统&#xff0c;让您的洗衣洗鞋服务体验更上一层楼。 干洗店管理系统是一款专为洗…