More effective C++:效率(2)

news2024/11/14 5:25:27
Item M21:通过重载避免隐式类型转换

通过函数重载来避免隐式类型转换带来的性能开销。当一个用户自定义类型(如 UPInt)与一个基本类型(如 int)一起使用算术运算符(如 +)时,C++ 编译器会尝试通过创建临时对象来进行隐式类型转换,以便能够调用相应的运算符重载函数。虽然这使得编程更加方便,但同时也带来了不必要的性能开销,因为每次都需要创建临时对象。

为了避免这种开销,建议显式地为不同类型的参数重载运算符。例如,对于 UPInt 类,可以重载 operator+ 以接受一个 UPInt 和一个 int 作为参数,或者两个 UPInt 作为参数。这样做可以让编译器直接选择正确的重载版本,而不是创建临时对象来匹配已有的重载版本。

class UPInt { // 无限精度整数类
public:
    UPInt();
    UPInt(int value);
    // 其他成员函数...
};

// 重载运算符以支持不同类型的参数
const UPInt operator+(const UPInt& lhs, const UPInt& rhs); // 加两个 UPInt
const UPInt operator+(const UPInt& lhs, int rhs);           // 加一个 UPInt 和一个 int
const UPInt operator+(int lhs, const UPInt& rhs);           // 加一个 int 和一个 UPInt
// 示例使用
UPInt upi1, upi2;
UPInt upi3 = upi1 + upi2; // 直接调用两个 UPInt 参数的重载
upi3 = upi1 + 10;         // 直接调用一个 UPInt 和一个 int 参数的重载
upi3 = 10 + upi2;         // 直接调用一个 int 和一个 UPInt 参数的重载

作者强调了一个重要的点,即不应该重载只有基本类型参数的运算符,如 const UPInt operator+(int lhs, int rhs);,因为 C++ 规定每个重载的运算符至少需要一个用户定义类型的参数。这是因为如果允许对基本类型重载运算符,可能会导致预定义行为的意外改变,进而引发程序错误。

如果允许程序员重载这些基本类型的运算符,那么就有可能改变这些基本类型的默认行为。如果允许重载 int + int,那么 2 + 2 可能不再等于 4,而是其他值,这会导致程序行为不可预测。

Item M22:考虑用运算符的赋值形式(op=)取代其单独形式(op)

整理后的代码及解释

1. 定义 Rational 类

首先,定义一个简单的 Rational 类,用于表示有理数,并实现 operator+= 和 operator-=:

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1) : num(numerator), den(denominator) {
        if (den == 0) {
            throw std::invalid_argument("Denominator cannot be zero");
        }
        normalize();
    }
    Rational& operator+=(const Rational& rhs) {
        num = num * rhs.den + rhs.num * den;
        den *= rhs.den;
        normalize();
        return *this;
    }
    Rational& operator-=(const Rational& rhs) {
        num = num * rhs.den - rhs.num * den;
        den *= rhs.den;
        normalize();
        return *this;
    }
private:
    void normalize() {
        int gcd = std::gcd(num, den);
        num /= gcd;
        den /= gcd;
    }
    int num; // 分子
    int den; // 分母
};

2. 实现 operator+ 和 operator- 通过 operator+= 和 operator-=

接下来,通过 operator+= 和 operator-= 来实现 operator+ 和 operator-:

const Rational operator+(const Rational& lhs, const Rational& rhs) {
    return Rational(lhs) += rhs;
}
const Rational operator-(const Rational& lhs, const Rational& rhs) {
    return Rational(lhs) -= rhs;
}

3. 使用模板实现通用的 operator+ 和 operator-

为了进一步简化代码,可以使用模板来实现通用的 operator+ 和 operator-:

template<class T>
const T operator+(const T& lhs, const T& rhs) {
    return T(lhs) += rhs;
}
template<class T>
const T operator-(const T& lhs, const T& rhs) {
    return T(lhs) -= rhs;
}

效率:operator+= 和 operator-= 是赋值运算符,它们直接在左操作数上进行操作,不需要创建临时对象。这使得它们比单独的 operator+ 和 operator- 更高效。

代码复用:通过 operator+= 和 operator-= 来实现 operator+ 和 operator-,可以减少代码重复,只需要维护 operator+= 和 operator-=。

避免临时对象:operator+ 和 operator- 通过创建临时对象来调用 operator+= 和 operator-=,但这仍然是一个高效的实现方式,因为临时对象的创建和销毁开销相对较小。

临时对象:operator+ 和 operator- 会创建临时对象,这可能会带来一些开销。但是,现代编译器通常会进行返回值优化(RVO),减少临时对象的创建和销毁开销。

使用未命名的临时对象(如 return T(lhs) += rhs;)通常比命名对象(如 T result(lhs); return result += rhs;)更高效,因为未命名对象更容易被编译器优化。

通过实现 operator+= 和 operator-=,并利用这些赋值运算符来实现 operator+ 和 operator-,可以确保代码的高效性和一致性

Item M23:考虑变更程序库

理想的程序库应该是短小、快速、强大、灵活、可扩展、直观、普遍适用、有良好支持、没有使用约束、没有错误的。但现实中不可能同时具备所有这些特性。不同的设计者会对这些特性赋予不同的优先级,因此即使是提供相同功能的程序库,其性能特征也可能完全不同。

iostream 和 stdio 的比较:

类型安全和可扩展性:iostream 是类型安全的,支持面向对象的扩展,而 stdio 则不具备这些特性。

性能:stdio 通常在执行速度和生成的执行文件大小上优于 iostream。作者通过一个基准测试(benchmark)程序验证了这一点,结果显示 stdio 在大多数情况下更快,有时甚至快很多。

代码实现:stdio 的高效性主要来自于其代码实现,特别是在运行时解析格式字符串的方式。而 iostream 在编译时确定操作数的类型,理论上可以更高效,但实际表现取决于具体的实现。

性能优化:

一旦找到软件的瓶颈(通过性能分析工具如 profiler),可以考虑更换程序库来消除瓶颈。例如,如果程序有 I/O 瓶颈,可以考虑用 stdio 替代 iostream;如果程序在动态内存分配和释放上花费大量时间,可以考虑使用其他 operator new 和 operator delete 的实现。

Item M24:理解虚拟函数、多继承、虚基类和 RTTI 所需的代价

1. 虚拟函数(Virtual Functions)

虚拟函数的实现通常依赖于虚拟表(vtbl)和虚拟表指针(vptr)。每个包含虚函数的类都有一个 vtbl,其中包含指向虚函数实现的指针。每个对象都有一个 vptr,指向其类的 vtbl。

性能和内存开销:对象大小:每个包含虚函数的对象都会有一个额外的 vptr,增加了对象的大小。

类数据:每个类需要一个 vtbl,其大小与类中声明的虚函数数量成正比。

函数调用开销:调用虚函数需要通过 vptr 查找 vtbl,再通过 vtbl 查找函数指针,这比调用非虚函数稍微复杂一些,但通常不是性能瓶颈。

内联限制:虚函数不能内联,因为其调用只能在运行时确定。

2. 多继承(Multiple Inheritance)

在多继承中,对象可能有多个 vptr,每个基类对应一个 vptr。每个基类可能有自己的 vtbl,派生类还需要生成特殊的 vtbl。

对象大小:多继承增加了对象的复杂性,对象中可能有多个 vptr,增加了对象的大小。

类数据:多继承增加了 vtbl 的数量和复杂性,增加了类数据的大小。

函数调用开销:多继承使得查找 vptr 和 vtbl 更复杂,增加了函数调用的开销。

3. 虚基类(Virtual Base Classes)

虚基类用于避免多继承时基类数据成员的重复。

实现虚基类通常需要在对象中添加额外的指针,指向虚基类。

对象大小:虚基类增加了对象的大小,因为需要额外的指针。

类数据:虚基类可能需要额外的 vtbl 和 vptr,增加了类数据的大小。

函数调用开销:虚基类使得对象布局更复杂,增加了函数调用的开销。

4. 运行时类型识别(RTTI)

RTTI 通过 typeid 操作符获取对象的类型信息。

类的 vtbl 中包含一个指向 type_info 对象的指针,用于存储类型信息。

对象大小:RTTI 本身不会增加对象的大小,因为它依赖于 vtbl。

类数据:每个类需要一个 type_info 对象,增加了类数据的大小。

函数调用开销:RTTI 的开销主要在于查找 type_info 对象,通常不是性能瓶颈。

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

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

相关文章

scala 迭代更新

在Scala中&#xff0c;迭代器&#xff08;Iterator&#xff09;是一种用于遍历集合&#xff08;如数组、列表、集合等&#xff09;的元素而不暴露其底层表示的对象。迭代器提供了一种统一的方法来访问集合中的元素&#xff0c;而无需关心集合的具体实现。 在Scala中&#xff0c…

部署zabbix遇到问题: cannot find a valid baseurl for repo:centos-sclo-rh/x86 64 怎么解决 ?

安装 Zabbix 前端包&#xff0c;提示cannot find a valid baseurl for repo&#xff1a;centos-sclo-rh/x86 64 安装zabbix前端包 # yum install zabbix-web-mysql-scl zabbix-apache-conf-scl 解决办法&#xff1a; 原因是&#xff1a;CentOS7的SCL源在2024年6月30日停止维护…

SpringBoot(十九)创建多模块Springboot项目(完整版)

之前我有记录过一次SpringBoot多模块项目的搭建,但是那一次只是做了一个小小的测试。只是把各模块联通之后就结束了。 最近要增加业务开发,要将目前的单模块项目改成多模块项目,我就参照了一下我上次搭建的流程,发现总是有报错。上次搭建的比较顺利,很多细枝末节也没有仔细…

基于Python的智能无人超市管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

什么是量化交易

课程大纲 内容初级初识量化&#xff0c;理解量化 初识量化 传统量化和AI量化的区别 量化思想挖掘 量化思想的挖掘及积累技巧 量化代码基础&#xff1a; python、pandas、SQL基础语法 金融数据分析 常用金融分析方式 常用因子分析方式 数据分析实战练习 回测及交易引擎 交易引擎…

xcode-select: error: tool ‘xcodebuild‘ requires Xcode, but active developer

打开 .sh 文件所在的终端窗口&#xff0c;执行终端命令&#xff1a;sh 文件名.sh&#xff0c;出现如下错误&#xff1a; 解决办法&#xff1a;

PETR/PETRv2/StreamPETR论文阅读

1. PETR PETR网络结构如下&#xff0c;主要包括image-backbone&#xff0c;3D Coordinates Generator&#xff0c;3D Position Encoder&#xff0c;transformer Decoder四个模块。 把N 个视角的图像输入到骨干网络中以提取 2D 多视图特征。在 3D 坐标生成器中&#xff0c;首先…

除了 Postman,还有什么好用的 API 调试工具吗

尽管 Postman 拥有团队协作等实用特性&#xff0c;其免费版提供的功能相对有限&#xff0c;而付费版的定价可能对小团队或个人开发者而言显得偏高。此外&#xff0c;Postman 的访问速度有时较慢&#xff0c;这可能严重影响使用体验。 鉴于这些限制&#xff0c;Apifox 成为了一…

程序员做自媒体,你所不了解的提词器,原来还有这么多大用处

程序猿的出路&#xff0c;不只是外卖员&#xff01; 你或许以为提词器只是个“背词神器”&#xff1f;实际上&#xff0c;它的应用范围早已超出你的想象。从直播到会议&#xff0c;从视频拍摄到户外采访&#xff0c;每一种场景都有对应的提词神器&#xff0c;帮你提升效率、避…

DeBiFormer实战:使用DeBiFormer实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

JS 实现WebSocket通讯和什么是WebSocket

WebSocket 介绍&#xff1a; WebSocket 是一种网络传输协议&#xff0c;可在单个 TCP 连接上进行全双工通信。它允许服务器主动向客户端推送信息&#xff0c;客户端也能实时接收服务器的响应。 客户端 这里实现了将input内的内容发送给客户端&#xff0c;并将接收到的服务器的…

前端 JS面向对象 继承

目录 一、ES5通过prototype来继承 二、ES6 class实现 一、ES5通过prototype来继承 const Person{eyes:2,head:1}function Woman(){}Woman.prototypePersonconst ladynew Woman()console.log(lady)function Man(){}Man.prototypePersonconst mannew Man()console.log(man) 打…

三菱FX5UPLC以太网Socket通信功能Passive开放的程序示例

Passive开放的通信流程如下所示。 参数设置 示例程序中使用的参数设置如下所示。 [CPU模块】 导航窗口↔[参数]↔[模块型号]↔[模块参数]-[以太网端口]-[基本设置]-[对象设备连接配置设置]↔[详细设置]→[以太网配置(内置以太网端口)]画面 【以太网模块】 [导航]中「参数]→[模…

UniApp 应用、页面与组件的生命周期详解

UniApp 应用、页面与组件的生命周期详解 在uni-app中包含了 应用生命周期、页面生命周期、和组件生命周期&#xff08; Vue.js的&#xff09;函数。 应用生命周期 应用生命周期仅可在App.vue中监听&#xff0c;在其它页面监听无效。 <script>export default {onLaunc…

Optimism掀起发链热潮,还有哪些发链 平台值得关注?

继电子巨头索尼在OP上发布L2 Soneium之后&#xff0c;10月29日&#xff0c;再质押协议巨头Swell宣布迁移至Optimism超级链&#xff0c;通过OP Stack构建Rollup加入OP生态系统。据DeFilama数据显示&#xff0c;Swell的TVL最高曾超过30亿美元&#xff0c;目前为13.4亿美元&#x…

0. 0:《跟着小王学Python·新手》

《跟着小王学Python新手》系列 《跟着小王学Python》 是一套精心设计的Python学习教程&#xff0c;适合各个层次的学习者。本教程从基础语法入手&#xff0c;逐步深入到高级应用&#xff0c;以实例驱动的方式&#xff0c;帮助学习者逐步掌握Python的核心概念。通过开发游戏、构…

如何优化Kafka消费者的性能

要优化 Kafka 消费者性能&#xff0c;你可以考虑以下策略&#xff1a; 并行消费&#xff1a;通过增加消费者组中的消费者数量来并行处理更多的消息&#xff0c;从而提升消费速度。 批量消费&#xff1a;配置 fetch.min.bytes 和 fetch.max.wait.ms 参数来控制批量消费的大小和…

Golang | Leetcode Golang题解之第556题下一个更大元素III

题目&#xff1a; 题解&#xff1a; func nextGreaterElement(n int) int {x, cnt : n, 1for ; x > 10 && x/10%10 > x%10; x / 10 {cnt}x / 10if x 0 {return -1}targetDigit : x % 10x2, cnt2 : n, 0for ; x2%10 < targetDigit; x2 / 10 {cnt2}x x2%10 -…

AscendC从入门到精通系列(一)初步感知AscendC

1 什么是AscendC Ascend C是CANN针对算子开发场景推出的编程语言&#xff0c;原生支持C和C标准规范&#xff0c;兼具开发效率和运行性能。基于Ascend C编写的算子程序&#xff0c;通过编译器编译和运行时调度&#xff0c;运行在昇腾AI处理器上。使用Ascend C&#xff0c;开发者…

unity基础,点乘叉乘。

简单记录下点乘叉乘&#xff0c;要不每次用完就忘&#xff0c;忘了又查。 using System.Collections; using System.Collections.Generic; using UnityEngine;public class TestCrossDot : MonoBehaviour {/// <summary>/// 原点/// </summary>public Transform t…