More Effective C++学习笔记(4)

news2024/11/25 15:18:39

目录

  • 条款16:谨记 80 - 20 法则
  • 条款17:考虑使用lazy evaluation(缓式评估)
  • 条款18:分期摊还预期的计算成本
  • 条款19:了解临时对象来源
  • 条款20:协助完成 “ 返回值优化 ”
  • 条款21:利用重载技术避免隐式类型转换
  • 条款22:考虑以操作符复合形式取代独身形式
  • 条款23:考虑使用其他程序库
  • 条款24:了解虚拟函数、多继承、虚基类和RTTI(运行时类型辨识)所需的代价

条款16:谨记 80 - 20 法则

  • 一个程序80%的资源用于20%的代码上。软件的整体性能几乎总是由构成要素的一小部分决定。你不仅需要找出造成问题的那一小段瓶颈所在,还必须找出办法来改善位于瓶颈处的程序性能
  • 大部分人采用“猜”的方法寻找瓶颈,有一定道理,更好地是借助一些程序分析器,测量每段代码运行时间、消耗的内存空间、函数被调用的频繁度(深究是否有临时对象产生)等所在意的资源
  • 借助可重现程序问题的数据帮忙解决问题。

条款17:考虑使用lazy evaluation(缓式评估)

  • 从效率的观点来看,最佳的计算就是根本不计算,关键在于拖延战术。当你使用了lazy evaluation后,采用此种方法的类将推迟计算工作直到系统确实需要这些计算的结果。如果不需要结果,将不用进行计算。
  • 区分读写:读操作效率远高于写操作(可能要先产生一个副本,然后再赋值);
  • 缓式取出:当生产一个体积很大的对象时,只生产对象的“外壳”,不从磁盘读数据。当对象内的某个字段被需要了,程序才从硬盘取回对应的字段数据。如下例,其中指针可以被换成智能指针。
class LargeObject {
public:
  LargeObject(ObjectID id); 
  const string& field1() const;
  int field2() const;
  double field3() const;
  const string& field4() const;
  ... 
private:
  ObjectID oid; 
  mutable string *field1Value;               //参见下面有关
  mutable int *field2Value;                  // "mutable"的讨论
  mutable double *field3Value;
  mutable string *field4Value;
  ... 
}; 
LargeObject::LargeObject(ObjectID id)
: oid(id), field1Value(0), field2Value(0), field3Value(0), ...
{} 
const string& LargeObject::field1() const
{
  if (field1Value == 0) {
    从数据库中为filed 1读取数据,使
    field1Value 指向这个值;
  } 
  return *field1Value;
}

当你试图在const成员函数里修改数据时,编译器会出现问题。最好的方法是声明字段指针为mutable,这表示在任何函数里它们都能被修改,甚至在const成员函数里,相当于告诉编译器“我知道我在干啥,没事儿!”。
如果不支持mutable,可以用**“冒牌this”法:产生一个非const指针指向this所指对象**:

const string& LargeObject::field1() const
{
  // 声明指针, fakeThis, 其与this指向同样的对象
  // 但是已经去掉了对象的常量属性
  LargeObject * const fakeThis =
    const_cast<LargeObject* const>(this); 
  if (field1Value == 0) {
    fakeThis->field1Value =                  // 这赋值是正确的,
      the appropriate data                   // 因为fakeThis指向的
      from the database;                     //对象不是const
  } 
  return *field1Value;
}
  • 当有大量的运算时(例如矩阵运算),可用自定义的数据结构简单的表示两者关系,但具体运算只在需要运算结果的时候进行计算,且大多数时候只需要所有运算结果中的一部分。
  • 如果你的计算式必要的,lazy evaluation并不会为你的程序节省任何工作或者时间,反而会因为增加了新的数据结构而增加了时间和内存消耗。只有当你的程序被要求执行某些计算,并且这些计算中有部分计算是可以避免的,lazy evaluation才有效

条款18:分期摊还预期的计算成本

  • over-eager evaluation:超前进度地做 “ 要求以外 ” 的更多工作。例如程序常常需要用到某种结果,我们在得到新数据就计算一次结果进行保存,这样真正要用的时候速度就会很快,降低每次计算的平均成本。
  • caching(缓存)那些已经被计算出来而以后还有可能需要的值
int findCubicleNumber(const string& employeeName)
{
  // 定义静态map,存储 (employee name, cubicle number)
  // pairs. 这个 map 是local cache。
  typedef map<string, int> CubicleMap;
  static CubicleMap cubes; 
  // try to find an entry for employeeName in the cache;
  // the STL iterator "it" will then point to the found
  // entry, if there is one (see Item 35 for details)
  CubicleMap::iterator it = cubes.find(employeeName); 
  if (it == cubes.end()) {
    int cubicle =
      the result of looking up employeeName's cubicle
      number in the database; 
    cubes[employeeName] = cubicle;           // add the pair
                                             // (employeeName, cubicle)
                                             // to the cache
    return cubicle;
  }
  else {
    return (*it).second;
  }
}

这个方法是使用局部缓存,用开销相对不大的内存中查询来替代开销较大的数据库查询。假如隔间号被不止一次地频繁需要,在findCubicleNumber内使用缓存会减少返回隔间号的平均开销。

  • Prefetching(预提取):每次划分出必须比要求更多的资源。例如磁盘控制器从磁盘读取数据时,经验显示如果需要一个地方的数据,则很可能也需要它旁边的数据,可一次性多读取些相邻的数据。动态数组会一次划分出比要求大小更大的数组,保证数组表面上是动态可扩容的。
  • 更快的速度经常会消耗更多的内存。Cache运算结果需要更多的内存,但是一旦需要被缓存的结果时就能减少需要重新生成的时间。Prefetch需要空间放置被prefetch的东西,但是它减少了访问它们所需的时间。

条款19:了解临时对象来源

  • 临时对象产生的两种情况:(1)为了使函数成功调用而进行隐式类型转换;(2)函数返回对象时
  • 函数参数隐式类型转换:当传入函数的实参和形参类型不符且编译器可以进行隐式转换时,编译器会以实参作为函数参数(例如char)所需类型(string)的构造函数(构造函数string(char))的参数,生成一个临时对象,作为对应同类型的函数形参,直到函数返回,临时对象被销毁。
// 返回ch在str中出现的次数
size_t countChar(const string& str, char ch); 
char buffer[MAX_STRING_LEN];
char c; 
// 读入到一个字符和字符串中,用setw
// 避免缓存溢出,当读取一个字符串时
cin >> c >> setw(MAX_STRING_LEN) >> buffer; 
cout << "There are " << countChar(buffer, c)
     << " occurrences of the character " << c
     << " in " << buffer << endl;
  • 仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些隐式类型转换。当传递一个非常量引用(reference-to-non-const)参数对象,就不会发生。C++语言禁止为非常量引用(reference-to-non-const)产生临时对象,因为非常量引用表明可能修改引用对象,如果允许隐式转换,那么引用的就是临时对象,而修改临时对象没有任何意义。
  • 对于大多数返回对象的函数来说,难以被另一个函数替代,从而没有办法避免构造和释放返回值对象。

条款20:协助完成 “ 返回值优化 ”

  • 采用返回指针的方法来避免临时对象生成可能会造成使用语法不自然(c = (ab))、资源泄漏(忘记删除指针对象)
const Rational * operator*(const Rational& lhs,
                           const Rational& rhs); 
Rational a = 10;
Rational b(1, 2); 
Rational c = *(a * b);             //你觉得这样很“正常”么?
  • 采用返回引用的方法可能会造成返回的引用已经被销毁
// 另一种危险的方法 (和不正确的)方法,用来
// 避免返回对象
const Rational& operator*(const Rational& lhs,
                          const Rational& rhs)
{
  Rational result(lhs.numerator() * rhs.numerator(),
                  lhs.denominator() * rhs.denominator());
  return result;//返回时,其指向的对象已经不存在了
}
  • 所以很大程度上无法避免使用by-value的方式返回对象。把你的努力引导到寻找减少返回对象的开销上来,而不是去消除对象本身
  • 可以使用某种特殊的写法来撰写函数,使它在返回对象时,能够让编译器自动进行优化,消除临时对象成本。如下例,产生一个临时对象,然后将复制临时对象,当作返回值。但是这样,编译器就会被允许消除在operator*内的临时变量和operator*返回的临时变量。它们能在为目标c分配的内存里构造return表达式定义的对象,只需要付出一个构造函数的成本。许多编译器都支持这一优化。:
// 一种高效和正确的方法,用来实现
// 返回对象的函数
const Rational operator*(const Rational& lhs,
                         const Rational& rhs)
{
  return Rational(lhs.numerator() * rhs.numerator(),
                  lhs.denominator() * rhs.denominator());
}
Rational a = 10;
Rational b(1, 2); 
Rational c = a * b;                          // 在这里调用operator*

条款21:利用重载技术避免隐式类型转换

  • 定义更多情况下的最佳匹配重载函数,使得编译器选择最佳匹配的重载函数调用,避免进行类型转换;
  • C++规定每个 “ 重载操作符 ” 必须获得至少一个 “ 用户自定义类型 ” 的自变量
  • 有时候增加一大堆重载函数不一定是件好事。

条款22:考虑以操作符复合形式取代独身形式

  • 确保操作符的复合形式(x+=1)与独身形式(x=x+1)之间的自然关系能够存在,可以以前者为基础实现后者,也便于维护。
class Rational {
public:
  ...
  Rational& operator+=(const Rational& rhs);
  Rational& operator-=(const Rational& rhs);
};
 
// operator+ 根据operator+=实现; 
const Rational operator+(const Rational& lhs,
                         const Rational& rhs)
{
  return Rational(lhs) += rhs;
} 
// operator- 根据 operator -= 来实现
const Rational operator-(const Rational& lhs,
                         const Rational& rhs)
{
  return Rational(lhs) -= rhs;
}
  • 一般而言,复合操作符比其独身形式效率更高,因为独身形式要返回一个新对象,从而在临时对象的构造和释放上有一些开销。然而,复合操作符可以经过编译器优化后把结果直接写到左边的参数里,因此不需要生成临时对象来容纳返回值。
  • 如果同时提供某个操作符的复合形式和独身形式,便允许你的客户在效率和便利性之间做取舍。如下代码,前者易撰写和维护,后者效率更高。
Rational a, b, c, d, result;
...
result = a + b + c + d;            // 可能用了3个临时对象
                             // 每个operator+ 调用使用1个 
还是这样编写:
result = a;                                  //不用临时对象
result += b;                                 //不用临时对象
result += c;                                 //不用临时对象
result += d;                                 //不用临时对象
  • 匿名对象比命名对象更容易清除,因此当我们面对在命名对象和临时对象间进行选择时,用临时对象更好一些。它使你耗费的开销不会比命名的对象还多。
//消耗一个匿名对象,编译器有机会实现“返回值优化”,把匿名对象直接写入接收函数返回对象的变量中
template<class T>
const T operator+(const T& lhs, const T& rhs)
{ return T(lhs) += rhs; }

//消耗一个命名对象result,函数调用完毕后被析构,没有机会消除这一消耗
template<class T>
const T operator+(const T& lhs, const T& rhs)
{
  T result(lhs);                        // 拷贝lhs 到 result中
  return result += rhs;                 // rhs与它相加并返回结果
}

条款23:考虑使用其他程序库

  • 具有相同功能的不同的程序库在性能上采取不同的权衡措施,所以一旦你找到软件的瓶颈,你应该知道是否可能通过替换程序库来消除瓶颈。比如如果你的程序有I/O瓶颈,你可以考虑用stdio替代iostream

条款24:了解虚拟函数、多继承、虚基类和RTTI(运行时类型辨识)所需的代价

  • 一个虚函数表vtbl通常是一个函数指针数组(或链表)。在程序中的每个类只要声明了虚函数或继承了虚函数,它就有自己的vtbl,并且类中vtbl的项目是指向虚函数实现体的函数指针。每个类保留一份虚函数表。
class C1 {
public:
  C1();
  virtual ~C1();
  virtual void f1();
  virtual int f2(char c) const;
  virtual void f3(const string& s);
  void f4() const;
  ...
};

在这里插入图片描述

class C2: public C1 {
public:
  C2();                                      // 非虚函数
  virtual ~C2();                             // 重定义函数
  virtual void f1();                         // 重定义函数
  virtual void f5(char *str);                // 新的虚函数
  ...
};

在这里插入图片描述

  • 虚函数指针vptr:每个声明了虚函数的对象都带有它,它是一个看不见的数据成员,指向对应类的virtual table。

  • 虚函数的三个成本:每个类都有一个虚函数表、每个有虚函数的对象都有一个虚函数指针、放弃inline(inline在编译器将调用函数展开,而虚函数在运行期才确定)。
    在这里插入图片描述

  • 多继承往往导致虚基类的需求。因为继承会导致继承体系的所有成员被继承被复制,对象体积迅速增大。虚基类可避免大量的复制现象,不过虚基类也会增加一定的成本,类对象中可能有多个虚指针(隐藏指针)。
    在这里插入图片描述在这里插入图片描述

  • 运行期类型辨别(RTTI):类的相关信息存储在类型为type_info的对象里,能通过使用typeid操作符访问一个类的type_info对象。RTTI耗费的空间是在每个类的vtbl中的占用的额外单元再加上存储type_info对象的空间。
    在这里插入图片描述

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

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

相关文章

余压监控在建筑加压送风系统中的应用

安科瑞 华楠 摘 要&#xff1a;从风量、风管和风口的设计、防烟部位正压控制方式等角度&#xff0c;分析了加压送风系统设计中需要注意的几点问题&#xff1a;风量的确定需合理有据&#xff1b;楼梯间风管和风口的设计需考虑均匀送风要求&#xff0c;前室风口设计确保不同楼层…

【地理图库】晨昏线示意图

声明&#xff1a;本资料整理&#xff0c;仅供学习&#xff01;

JNPF快速开发平台:释放您的创造力,加速企业数字化转型

一、前言 在当今的数字化时代&#xff0c;企业需要快速响应市场变化&#xff0c;不断迭代和优化业务流程&#xff0c;以保持竞争力。然而&#xff0c;传统软件开发方式往往无法满足企业对于快速开发和灵活部署的需求。JNPF快速开发平台旨在解决这一问题&#xff0c;为企业提供高…

CCF HPC China2023 | 盛大开幕,邀您关注澎峰科技

2023年8月24日&#xff0c;以“算力互联智领未来”为主题的第十九届全国高性能计算学术年会&#xff08;CCF HPC China 2023&#xff09;在青岛红岛国际会议展览中心拉开帷幕。特邀嘉宾涵盖行业大咖&#xff0c;主持阵容同样是“重量级”——来自国家并行计算机工程技术研究中心…

如何快速打造线上展厅?线上展厅可以用在哪些行业?

引言&#xff1a; 随着科技的迅速发展&#xff0c;线上展厅作为一种创新的营销方式&#xff0c;正逐渐引领着不同行业的营销风潮。线上展厅以其独特的虚拟体验&#xff0c;为企业搭建了一个无界限的展示平台&#xff0c;让用户能够在虚拟空间中感受产品、服务与品牌文化。 一…

Java对象的初始化顺序

对象的初始化顺序(父类永远在子类前面&#xff0c;属性和方法谁写在上边谁先执行)&#xff1a; 静态(只执行一次)--->非静态--->构造方法 静态成员和static块-->普通成员和非static块-->构造函数。 存在继承&#xff1a; 父类静态成员和static块-->子类静态成…

Resnet模型详解

1、Resnet是什么&#xff1f; Resnet是一种深度神经网络架构&#xff0c;被广泛用于计算机视觉任务&#xff0c;特别是图像分类。它是由微软研究院的研究员于2015年提出的&#xff0c;是深度学习领域的重要里程碑之一。 2、网络退化问题 理论上来讲&#xff0c;随着网络的层…

干货分享:可证明安全的隐私计算

“隐语”是开源的可信隐私计算框架&#xff0c;内置 MPC、TEE、同态等多种密态计算虚拟设备供灵活选择&#xff0c;提供丰富的联邦学习算法和差分隐私机制 开源项目&#xff1a;github.com/secretflowgitee.com/secretflow 以下文章来源于DataFunTalk &#xff0c;作者洪澄 D…

【实训项目】易行APP设计(c2c二手交易平台)

1.设计摘要 1.1市场背景 随着经济的迅速发展与科技日新月异的进步&#xff0c;家庭内的各项物品更新换代频率越来越快&#xff0c;人们购买新商品后越来越多旧的商品积压需要处理&#xff1b;在互联网电商的各种营销刺激下&#xff0c;消费者非常容易形成“冲动”消费&#x…

【Spring】一次性打包学透 Spring | 阿Q送书第五期

文章目录 如何竭尽可能确保大家学透Spring1. 内容全面且细致2. 主题实用且本土化3. 案例系统且完善4. 知识有趣且深刻 关于作者丁雪丰业内专家推图书热卖留言提前获赠书 不知从何时开始&#xff0c;Spring 这个词开始频繁地出现在 Java 服务端开发者的日常工作中&#xff0c;很…

leetcode:只出现一次的数字Ⅲ(详解)

题目&#xff1a; 给你一个整数数组 nums&#xff0c;其中恰好有两个元素只出现一次&#xff0c;其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。 你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。 示例 1&…

没消费?复购难?不如试试即拼七人拼团模式

在当今的社会环境下&#xff0c;快消品、大健康、美容等行业一直是人们生活中不可或缺的一部分。它们各具特色&#xff0c;不断满足大众的需求&#xff0c;因此受到广泛欢迎。但由于市场品种繁多、竞争激烈&#xff0c;消费者的选择也变得更加多样化。为了提高各行业的竞争性&a…

EPS导出为带cass属性的dwg文件

1、在EPS软件中打开一幅画好的地形图&#xff0c;如下&#xff1a; 2、点击菜单栏里面的工具--运行脚本--数据转换--cass10.具体操作如下&#xff1a; 3、设置输出模式&#xff0c;具体如下图。 4、输出结果如下&#xff0c;在cass中打开输出的dwg文件&#xff0c;结果如下&…

多核异构核间通信Mailbox vs rpmsg

目录 一、关键术语解释 二、Mailbox与rpmsg对比 三、rpmsg传输流程 异构核间数据通过共享内存实现数据传递&#xff0c;通过中断来触发发送、接收。 一、关键术语解释 IPC Inter-Processor Communication MailBox IP which provides queued interrupt mechanism for comm…

压力传感器模拟信号(频率)转数字信号的问题

压力传感器模拟信号&#xff08;频率&#xff09;转数字信号的问题 三河凡科科技飞讯教学篇&#xff1a;压力传感器模拟信号&#xff08;频率&#xff09;转数字信号是现代控制系统中十分重要的一个问题。在许多工业应用中&#xff0c;压力传感器模拟信号需要被准确地转换成数…

MyBatis进阶:告别SQL注入!MyBatis分页与特殊字符的正确使用方式

目录 引言 一、使用正确的方式实现分页 1.1.什么是分页 1.2.MyBatis中的分页实现方式 1.3.避免SQL注入的技巧 二、特殊字符的正确使用方式 2.1.什么是特殊字符 2.2.特殊字符在SQL查询中的作用 2.3.如何避免特殊字符引起的问题 2.3.1.使用CDATA区段 2.3.2.使用实体引…

微服务中间件--MQ服务异步通信

MQ服务异步通信 MQ服务异步通信a.消息可靠性1) 生产者消息确认2) 消息持久化3) 消费者消息确认4) 消费者失败重试4.a) 本地重试4.b) 失败策略 b.死信交换机1) 初识死信交换机2) TTL3) 延迟队列a) 安装延迟队列插件b) SpringAMQP使用延迟队列插件 c.惰性队列1) 消息堆积问题2) 惰…

redis实战-缓存三剑客穿透击穿雪崩解决方案

缓存穿透 定义 缓存穿透 &#xff1a;缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库&#xff0c;造成数据库压力&#xff0c;也让缓存没有发挥出应有的作用 解决方案 缓存空对象 当我们客户端…

《Go 语言第一课》课程学习笔记(十一)

控制结构 if 的“快乐路径”原则 针对程序的分支结构&#xff0c;Go 提供了 if 和 switch-case 两种语句形式&#xff1b;而针对循环结构&#xff0c;Go 只保留了 for 这一种循环语句形式。 if 语句 if 语句是 Go 语言中提供的一种分支控制结构&#xff0c;它也是 Go 中最常…

人机对抗智能-部分可观测异步智能体协同(POAC)

环境链接&#xff1a;数据中心-人机对抗智能 (ia.ac.cn)http://turingai.ia.ac.cn/data_center/show/10 1.环境配置 Ubuntu 20.04 Anaconda python版本3.6 1.1 安装torch0.4.1失败 参考文章&#xff1a; 安装torch0.4.1的神坑_torch0.4.1_DEMO_Tian的博客-CSDN博客 co…