Effective C++ 2e Item23

WinDbg调试工具安装全攻略:从下载到配置的保姆级教程(Windows 10/11适用) 本文提供了一份详细的WinDbg调试工具安装与配置指南,适用于Windows 10/11用户。文章对比了WinDbg Preview独立版与SDK内置经典版的区别,并手把手指导从Microsoft Store或Windows SDK完成安装。核心部分重点讲解了如何配置符号文件路径与源代码路径,这是让WinDbg发挥强大调试功能的关键步骤,并针对常见安装问题提供了实用的排错方法。 阅读详情

条款23: 必须返回一个对象时不要试图返回一个引用

据说爱因斯坦曾提出过这样的建议:尽可能地让事情简单,但不要过于简单。在C++语言中相似的说法应该是:尽可能地使程序高效,但不要过于高效。

一旦程序员抓住了“传值”在效率上的把柄(参见条款22),他们会变得十分极端,恨不得挖出每一个隐藏在程序中的传值操作。岂不知,在他们不懈地追求纯粹的“传引用”的过程中,他们会不可避免地犯另一个严重的错误:传递一个并不存在的对象的引用。这就不是好事了。

看一个表示有理数的类,其中包含一个友元函数,用于两个有理数相乘:

class Rational {
public:
  Rational(int numerator = 0, int denominator = 1);

  ...

private:
  int n, d;              // 分子和分母

friend
  const Rational                      // 参见条款21:为什么
    operator*(const Rational& lhs,    // 返回值是const
              const Rational& rhs)    
};

inline const Rational operator*(const Rational& lhs,
                                const Rational& rhs)
{
  return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

很明显,这个版本的operator*是通过传值返回对象结果,如果不去考虑对象构造和析构时的开销,你就是在逃避作为一个程序员的责任。另外一件很明显的事实是,除非确实有必要,否则谁都不愿意承担这样一个临时对象的开销。那么,问题就归结于:确实有必要吗?

答案是,如果能返回一个引用,当然就没有必要。但请记住,引用只是一个名字,一个其它某个已经存在的对象的名字。无论何时看到一个引用的声明,就要立即问自己:它的另一个名字是什么呢?因为它必然还有另外一个什么名字(见条款M1)。拿operator*来说,如果函数要返回一个引用,那它返回的必须是其它某个已经存在的Rational对象的引用,这个对象包含了两个对象相乘的结果。

但,期望在调用operator*之前有这样一个对象存在是没道理的。也就是说,如果有下面的代码:

Rational a(1, 2);                // a = 1/2
Rational b(3, 5);                // b = 3/5
Rational c = a * b;              // c 为 3/10

期望已经存在一个值为3/10的有理数是不现实的。如果operator* 一定要返回这样一个数的引用,就必须自己创建这个数的对象。

一个函数只能有两种方法创建一个新对象:在堆栈里或在堆上。在堆栈里创建对象时伴随着一个局部变量的定义,采用这种方法,就要这样写operator*:

// 写此函数的第一个错误方法
inline const Rational& operator*(const Rational& lhs,
                                 const Rational& rhs)
{
  Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
  return result;
}

这个方法应该被否决,因为我们的目标是避免构造函数被调用,但result必须要象其它对象一样被构造。另外,这个函数还有另外一个更严重的问题,它返回的是一个局部对象的引用,关于这个错误,条款31进行了深入的讨论。

那么,在堆上创建一个对象然后返回它的引用呢?基于堆的对象是通过使用new产生的,所以应该这样写operator*:

// 写此函数的第二个错误方法
inline const Rational& operator*(const Rational& lhs,
                                 const Rational& rhs)
{
  Rational *result =
    new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}

首先,你还是得负担构造函数调用的开销,因为new分配的内存是通过调用一个适当的构造函数来初始化的(见条款5和M8)。另外,还有一个问题:谁将负责用delete来删除掉new生成的对象呢?

实际上,这绝对是一个内存泄漏。即使可以说服operator*的调用者去取函数返回值地址,然后用delete去删除它(绝对不可能——条款31展示了这样的代码会是什么样的),但一些复杂的表达式会产生没有名字的临时值,程序员是不可能得到的。例如:

Rational w, x, y, z;

w = x * y * z;

两个对operator*的调用都产生了没有名字的临时值,程序员无法看到,因而无法删除。(再次参见条款31)

也许,你会想你比一般的熊——或一般的程序员——要聪明;也许,你注意到在堆栈和堆上创建对象的方法避免不了对构造函数的调用;也许,你想起了我们最初的目标是为了避免这种对构造函数的调用;也许,你有个办法可以只用一个构造函数来搞掂一切;也许,你的眼前出现了这样一段代码:operator*返回一个“在函数内部定义的静态Rational对象”的引用:

// 写此函数的第三个错误方法
inline const Rational& operator*(const Rational& lhs,
                                 const Rational& rhs)
{
  static Rational result;      // 将要作为引用返回的
                               // 静态对象

  lhs和rhs 相乘,结果放进result;

  return result;
}

这个方法看起来好象有戏,虽然在实际实现上面的伪代码时你会发现,不调用一个Rational构造函数是不可能给出result的正确值的,而避免这样的调用正是我们要谈论的主题。就算你实现了上面的伪代码,但,你再聪明也不能最终挽救这个不幸的设计。

想知道为什么,看看下面这段写得很合理的用户代码:

bool operator==(const Rational& lhs,      // Rationals的operator==
                const Rational& rhs);     //

Rational a, b, c, d;

...

if ((a * b) == (c * d)) {

  处理相等的情况;

} else {

  处理不相等的情况;

}

看出来了吗?((a*b) == (c*d)) 会永远为true,不管a,b,c和d是什么值!

用等价的函数形式重写上面的相等判断语句就很容易明白发生这一可恶行为的原因了:

if (operator==(operator*(a, b), operator*(c, d)))

注意当operator==被调用时,总有两个operator*刚被调用,每个调用返回operator*内部的静态Rational对象的引用。于是,上面的语句实际上是请求operator==对“operator*内部的静态Rational对象的值”和“operator*内部的静态Rational对象的值”进行比较,这样的比较不相等才怪呢!

幸运的话,我以上的说明应该足以说服你:想“在象operator*这样的函数里返回一个引用”实际上是在浪费时间。但我没幼稚到会相信幸运总会光临自己。一些人——你们知道这些人是指谁——此刻会在想,“唔,上面那个方法,如果一个静态变量不够用,也许可以用一个静态数组……”

请就此打住!我们难道还没受够吗?

我不能让自己写一段示例代码来太高这个设计,因为即使只抱有上面这种想法都足以令人感到羞愧。首先,你必须选择一个n,指定数组的大小。如果n太小,就会没地方储存函数返回值,这和我们前面否定的那个“采用单个静态变量的设计”相比没有什么改进。如果n太大,就会降低程序的性能,因为函数第一次被调用时数组中每个对象都要被创建。这会带来n个构造函数和n个析构函数的开销,即使这个函数只被调用一次。如果说"optimization"(最优化)是指提高软件的性能的过程, 那么现在这种做法简直可以称为"pessimization"(最差化)。最后,想想怎么把需要的值放到数组的对象中以及需要多大的开销?在对象间传值的最直接的方法是通过赋值,但赋值的开销又有多大呢?一般来说,它相当于调用一个析构函数(摧毁旧值)再加上调用一个构造函数(拷贝新值)。但我们现在的目标正是为了避免构造和析构的开销啊!面对现实吧:这个方法也绝对不能选用。

所以,写一个必须返回一个新对象的函数的正确方法就是让这个函数返回一个新对象。对于Rational的operator*来说,这意味着要不就是下面的代码(就是最初看到的那段代码),要不就是本质上和它等价的代码:

inline const Rational operator*(const Rational& lhs,
                                const Rational& rhs)
{
  return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

的确,这会导致“operator*的返回值构造和析构时带来的开销”,但归根结底它只是用小的代价换来正确的程序运行行为而已。况且,你所担心的开销还有可能永远不会出现:和所有程序设计语言一样,C++允许编译器的设计者采用一些优化措施来提高所生成的代码的性能,所以,在有些场合,operator*的返回值会被安全地除去(见条款M20)。当编译器采用了这种优化时(当前大部分编译器这么做),程序和以前一样继续工作,只不过是运行速度比你预计的要快而已。

以上讨论可以归结为:当需要在返回引用和返回对象间做决定时,你的职责是选择可以完成正确功能的那个。至于怎么让这个选择所产生的代价尽可能的小,那是编译器的生产商去想的事。

将 SAP BTP ABAP environment 与 SAP S/4HANA Cloud 公有云版集成:从架构理念到落地实战 本文探讨了在SAP云环境中实现ABAP扩展应用与S/4HANA Cloud系统集成的技术方案。通过建立标准化的远程通信通道,开发者可在SAP BTP ABAP环境中开发扩展应用,同时安全访问S/4HANA Cloud的核心业务数据和服务。文章详细介绍了整体架构设计、开发实现步骤(包括定义通信场景、生成服务消费模型和编写调用代码)以及管理员在S/4HANA侧的配置工作(创建通信安排和技术用户)。通过一个业务伙伴自助注册平台的案例,展示了如何在不修改标准系统的情况下实现云扩展,体现了SAP"干净核心& 阅读详情

相关推荐

CAN开发笔记(TI开发者推荐笔记)

查阅了很多资料没有查阅到关于5728芯片CAN模块寄存器的介绍,希望通过本文能给做CAN驱动的后来者有一点参考意义,我也是刚接触CAN模块有些不足请各位多多指正。 由下图所示是CAN模块初始化流程图 首先要开始初始化,设置CAN位时间,然后配置message objects, 完成初始化。 1.开始初始化 首先要控制DCAN_CTL 寄存器,该寄存器是CAN模块的总控制器,很多配置通过...

weixin_44316996的博客 3563

Java知识点总结(六)二进制I/O

六、二进制I/O 参考《Java语言程序设计基础篇第10版》总结 17..二进制I/O

weixin_36927767的博客 822

从机械到数字:EC11旋转编码器与ESP32的PWM控制艺术

本文详细解析了EC11旋转编码器与ESP32的PWM控制技术,探讨了如何将机械旋转动作转换为精确的数字信号控制。通过硬件连接、信号处理和软件优化等实践技巧,开发者可以构建高效的人机交互系统,适用于智能照明、电机控制等多种应用场景。

android的专栏 786

winform异常,无法访问已释放的对象

这个异常的意思是你在使用一个已经被释放的对象,而这个对象已经不再可用了。 这个错误通常是由于你在程序中使用了一个已经被销毁或者释放的对象,例如在一个已经关闭的窗口中访问控件或者在一个已经释放的数据库连接中执行查询等。 为了解决这个问题,你需要检查你的代码,确保在使用对象之前对象是可用的,并且在不再使用对象之后及时释放它。 如果你无法定位到具体的问题所在,你可以使用调试工具来帮助你定位错误的位置。 ...

weixin_35756690的博客 1615

2006NOIP普及组真题 4. 数列

从上述推理中,我们发现要输出的幂次和中的第 N 项(也就是幂次和中的序号N),对应的二进制位数和幂次和对应的二进制位数相同。当 k=3 时,k的幂次为1, 3, 9, 27, 81…,即第0位,第2位均为1,转成 k 进制(k=3)时。,即第2位,第3位均为1,转成 k 进制(k=3)时。转成 k 进制(k=3)时的幂次和为。,即第0位,第1位,第2位均为1,(即第0位,第1位,第2位均为1)N=12时,12的二进制是。(即第0位,第2位均为1)(即第2位,第3位均为1)结论:这道题就变成了。

weixin_40252159的博客 1514

LVGL 离线字体转换lv_font_conv本地安装

LVGL FONT CONV 离线版本

liu132641的专栏 3389

关于解决NX重置之后“试图使用不活动的对象”

  在执行Mian函数之前设置好私有字段,根据代码执行顺序Mian函数入口-构造函数-initialize(初始化对象)-dialogShown-Update 对话框中已经选中对象之后就已经创建了Feature(特征)并且已经储存在了私有字段中,重置之后自动进入初始化之前的函数         if (BlockFeature != null)         {             t...

Eric__Cheng的博客 1470

从DeepMimic到虚拟偶像:用动作捕捉数据训练AI跳舞的完整流程(附Blender插件配置)

本文详细介绍了如何利用DeepMimic的深度强化学习框架,将动作捕捉数据转化为虚拟偶像或游戏角色的舞蹈动作。从核心的模仿学习理念、PyBullet环境搭建、策略网络与奖励函数设计,到数据处理、训练技巧(如参考状态初始化),最后讲解了如何将训练好的AI模型集成到Blender和Unity等工具中,实现从数据到实时驱动角色的完整流程。

weixin_29054399的博客 504

向日葵远程协助软件提示“加入列表失败,主机不存在或已删除”、“被控服务连接失败”处理办法

打开控制面板——程序——程序和功能——启用或关闭windows功能 启用Telent Clinet——确定

weixin_48388090的博客 3万+

电机控制基础,小白入门篇

电机,这个藏在风扇、电动车、机器人里的“动力心脏”,如何精准控制它的转速、方向甚至力量?这篇博客专为完全零基础的小白打造,用“说人话”的方式拆解电机控制的底层逻辑,帮你从“看不懂电路图”到“能调个简单电机”。 电机控制不是“大神专属”,只要掌握“电生磁、磁生力”的底层逻辑,加上动手实践,小白也能快速入门。这篇博客会陪你从“看电机转”到“让电机听话”,把复杂的控制原理变成能上手玩的“小实验”。准备好你的面包板和电机,我们一起开启“电控之旅”吧!

汽车电子软件领域知识分享 5618

语言大模型之Tokenization的深入理解

Tokenization(分词)是自然语言处理(NLP)中一个非常重要的步骤,它的目标是将文本转换为模型可以理解的格式。对于像GPT、BERT等大型预训练模型来说,tokenization决定了文本如何转化为数字表示,从而影响模型在不同任务中的表现。接下来我会深入介绍tokenization的过程、不同的tokenization方法,以及它与模型微调(fine-tuning)之间的关系。

JustinMars的博客 343

Effective C++ 2e Item31

条款31: 千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用本条款听起来很复杂,其实不然。它只是一个很简单的道理,真的,相信我。先看第一种情况:返回一个局部对象的引用。它的问题在于,局部对象 ----- 顾名思义 ---- 仅仅是局部的。也就是说,局部对象是在被定义时创建,在离开生命空间时被销毁的。所谓生命空间,是指它们所在的函数体。当函数返回时,程序的控制离开了这

lostmouse的专栏 1058

4、风险管理、信息安全与信息管理的核心原则

本文深入探讨了风险管理、信息安全与信息管理的核心原则,涵盖Risk IT框架、ISO 31000标准以及信息安全与信息管理的关键原则。通过案例分析与实际应用挑战的解析,帮助组织更好地应对数字化时代的风险与安全问题,实现可持续发展。

bread的博客 120

Effective C++ 2e Item19

 条款19: 分清成员函数,非成员函数和友元函数成员函数和非成员函数最大的区别在于成员函数可以是虚拟的而非成员函数不行。所以,如果有个函数必须进行动态绑定(见条款38),就要采用虚拟函数,而虚拟函数必定是某个类的成员函数。关于这一点就这么简单。如果函数不必是虚拟的,情况就稍微复杂一点。看下面表示有理数的一个类:class Rational {public:  Rational(in

lostmouse的专栏 2323

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Effective C++ 2e Item2

 条款2:尽量用而不用是的,scanf和printf很轻巧,很高效,你也早就知道怎么用它们,这我承认。但尽管他们很有用,事实上scanf和printf及其系列还可以做些改进。尤其是,他们不是类型安全的,而且没有扩展性。因为类型安全和扩展性是C++的基石,所以你也要服从这一点。另外,scanf/printf系列函数把要读写的变量和控制读写格式的信息分开来,就象古老的FORTRAN那样。是该向

lostmouse的专栏 1069

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Item 5:那些被C++默默地声明和调用的函数 Effective C++笔记

Item 5: Know what functions C++ silently writes and calls 在C++中,编译器会自动生成一些你没有显式定义的函数,它们包括:构造函数、析构函数、复制构造函数、=运算符。 有时为了符合既有设计,我们不希望自动生成这些函数,我们可以把它们显式声明为private。 此时在使用这些类的客户看来,它们就像不存在一样。 clas

分享技术,共建开放平台 1005

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

[翻译] Effective C++, 3rd Edition, 附录 B. 第二和第三版之间的 Item 映射

附录 B. 第二和第三版之间的 Item 映射作者:Scott Meyers译者:fatalerror99 (iTePubs Nirvana)发布:http://blog.youkuaiyun.com/fatalerror99/Effective C++ 的第三版在很多方面与第二版不同,其中最引人注目的是它包含很多新的信息。然而,第二版的大部分内容依然保留在第三版中,虽然经常会改变形式和位置

艺术编程 5819

Effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)

假设你正在操作一个Rectangle类。每个矩形可以通过左上角的点和右下角的点来表示。为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放入一个辅助结构体中,Rectangle中声明一个指向它的指针就可以了: 1 class Point { // class for repre

小小小程序员的博客 218

读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字

1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息。消息可以以加密或者明文(未加密)的方式被发送。如果在编译阶段我们有足够的信息来确定哪个信息会被发送到哪个公司,我们可以使用基于模板的解决方案: 1 class CompanyA { 2 public: 3 ... 4 void sendCleartext(cons...

weixin_34406086的博客 116

读书笔记 effective c++ Item 10 让赋值运算符返回指向*this的引用

一个关于赋值的有趣的事情是你可以将它们链在一起: 1 int x, y, z; 2 3 x = y = z = 15; // chain of assignments 同样有趣的是赋值采用右结合律,所以上面的赋值链被解析成下面这个样子: 1 x = (y = (z = 15)); 在这里,15被赋值给z,然后赋值的结果(更新的z)被赋值给y,再然后赋值的结果(更新的Y)被赋值给x。 ...

HarlanC的专栏 152

读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)...

假设你正在操作一个Rectangle类。每个矩形可以通过左上角的点和右下角的点来表示。为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放入一个辅助结构体中,Rectangle中声明一个指向它的指针就可以了: 1 class Point { // class for re...

weixin_34064653的博客 121
上一篇: Effective C++ 2e Item22
下一篇: Effective C++ 2e Item24
lostmouse
博客等级 码龄26年 22粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值