《Effective C++》资源管理:条款20-条款21

Effective C++ 第三版 所有条款总结及对应代码描述(纯干货) Effective C++ 第三版 所有条款总结及对应代码描述(纯干货) 阅读详情

条款20:宁以pass-by-reference-to-const替换pass-by-value

在默认情况下,C++函数传递参数是继承C的方式,是值传递(pass by value)。这样传递的都是实际实参的副本,这个副本是通过调用复制构造函数来创建的。有时候创建副本代价非常昂贵。例如一下继承体系

<span style="font-size:14px;">class Person{
public:
	Person();
	virtual ~Person();
	……
private:
	std::string name;
	std::string address;
};
class Student:public Person{
public:
	Student();
	~Student();
	……
private:
	std::string schoolName;
	std::string schoolAddress;
};</span>

现在考虑一个函数validateStudent,它需要一个Student实参,以pass by value方式传递。
<span style="font-size:14px;">bool validateStudent(Student s);//pass by value
Student plato;
bool platIsOK=validateStudent(plato);</span>

当函数被调用时,copy构造函数会被调用,用plato构造s。在返回时,s会被析构。那么pass by value的代价就是Student的一次构造和一次析构。但是Student构造和析构时又发生了什么?它内部有两个string对象,所以会有两个string对象的构造和析构。Student继承自Person,又加上Person的构造和析构,Person内又有两个string对象,因此还要加上2个string对象的构造和析构。总共是六次构造和六次析构。

pass  by value是正确的,但是其效率低下。以pass by reference-to-const方式传递,可以回避所有构造函数和析构函数。

<span style="font-size:14px;">bool validateStudent(const Student& s);</span>
这种方式传递,没有新对象创建,所以自然没有构造和析构函数的调用。参数中,以const修饰是比较重要的,原先的pass by value,原先的值自然不会被修改。现在以pass by reference方式传递,函数validateStudent内使用的对象和传进来的同同一个对象,为了防止在函数内修改,加上const限制。

以pass by reference方式传递,还可以避免对象切割(slicing)问题。一个派生类(derived class)对象以pass by value方式传递,当被视为一个基类对象(base class)时,基类对象的copy构造函数会被调用,此时派生类部分全部被切割掉了,仅仅留下一个base class部分。

在C++编译器的底层,reference往往以指针实现出来,所以pass by reference通常意味着真正传递是指针。但是对于内置类型,pass by value往往比pass by reference更高效。所以在使用STL函数和迭代器时,习惯上都被设计出pass by value。当设计迭代器和函数时,设计者有责任查看哪种传递方式更为高效,是否会有切割问题的影响。这个规则的改变适用于你使用C++的哪一部分。(条款1)

通常,内置类型都比较小,因此有人认小型types都适合pass by value,用户自己定义的class亦然。但是对象小并不意味着copy构造函数代价小。许多对象(包括STL容器),内涵的成员只不过是一两个指针,但是复制这种对象时,要复制指针指向的每一样东西,这个代价很可能十分昂贵。

还有一个理由,某些编译器对待内置类型和用户自定义类型的”态度“截然不同,即使两者有着相同的底层描述。例如,某些编译器拒绝把一个double组成的对象放进缓存器内,但是却乐意在一个正规基础上光秃秃的doubles上这么做。当这种事情发生时,应该以by reference方式传递此对象,因为编译器当然会把指针放进缓存器。

用户自定义的小型types,可能还会发生变化,将来也许会变大,其内部实现可能会改变,所以用户自定义的小型type在使用pass by value时要慎重。

一般情况下,可以假设内置类型和STL迭代器和函数对象以pass by value是代价不昂贵。其他时候最好以pass by reference to const替换掉pass by value。

条款21:必须返回对象时,别妄想返回其reference

在掌握了pass by reference后,刚开始一心一意想把所有pass by value替换为pass by reference。这时往往会犯下一个错误:传递一些reference指向不存在的对象。考虑一个用以表现有理数乘积的class。

<span style="font-size:14px;">class Rational{
public:
	Rational(int numerator=0, int denominator=1);
	……
private:
	int n, d;
	friend
	const Rational operator*(const Rational& lhs,
							 const Rational& rhs);
};</span>

这个版本的operator*用by value的方式返回其计算结果(对象)。这样返回的代价是一个对象的创建+析构+另一个对象的创建。

一个对象的创建是指,在这个operator*函数中,创建一个新对象来保存结果,之后用这个用这个新对象返回,返回时用它初始化另一个对象。之后这个新对象析构。

但是如果用by reference方式传递就不会有任何代价。但是reference只是名称,代表一个已经存在的对象,任何时候看到reference都应该问自己,它的另一个名称是什么?如果上述operator*返回一个reference,那么它一定指向一个存在的Rational对象。

<span style="font-size:14px;">Rational a(1, 2);// 1/2
Rational b(3, 5);// 3/5
Rational c=a*b;// 3/10</span>

这时返回一个值为3/10的Rational对象。但是这个对象原先并不存在,这时如果返回reference,那么必须在函数operator*内创建这个Rational对象。

函数创建新对象有两种途径,在stack上或在heap上创建。如果定义local变量,那么就在stack上创建

<span style="font-size:14px;">const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
	Rational result(lhs.n* rhs.n, lhs.d* rhs.d);
	return result;
}</span>

上面的做法,没有避免调用构造函数,result像任何对象一样由构造函数构造起来。上面还有一个错误:这个函数返回reference执行result,但是result是个local对象,它在函数退出前被销毁了。

考虑在heap上创建对象

<span style="font-size:14px;">const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
	Rational* result=new Rational(lhs.n* rhs.n, lhs.d* rhs.d);
	return *result;
}</span>

还是要付出一个构造函数的代价,分配的内存将以一个适当的构造函数初始化。现在又面临另一个问题:谁负责给你new出来的对象实施delete?

即使客户使用时谨慎,但是还是有可能造成以下内存泄露:

<span style="font-size:14px;">Rational w,x,y,z;
w=x*y*z;</span>
上述语句调用了两次operator*,使用了两次new,也就需要两次delete。但是没办法让使用者进行那些delete操作,因为没有让他们取得operator*返回的reference背后的指针。这会导致内存泄露。

在上面的两种做法中(在stack和在heap上创建对象),都因为operator*返回结果调用构造函数而受到惩罚。我们最初的目标是避免如此的构造函数的调用。还有一个办法避免任何构造函数的调用:让operator*返回一个指向在函数内部定义的static Rational对象:

<span style="font-size:14px;">const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
	static Rational result;
	result=……;
	return result;
}</span>

暂且不说上述代码在多线程时有什么问题,先看一下下面的调用的有什么问题:
<span style="font-size:14px;">bool operator==(const Rational& lhs, const Rational& rhs);//为比较Rational对象而写的
Rational a, b, c, d;
……
if((a*b)==(c*d))
{//乘积相等时,
	doSomething();
}
else//乘积不等时
{
	doOtherthing();
}</span>

上述表达式(a*b)==(c*d)总是返回true。因为operator*返回的对象都是指向operator*内部定义的static对象。这个对象只有一个,当计算后者时,前者被覆盖。因此永远是两个相同的Rational对象的作比较。

一个必须返回新对象的函数的正确写法,就是让那个函数返回一个新对象。就这么简单。上述operator*正确的写法:

<span style="font-size:14px;">inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs.n* rhs.n, lhs.d* rhs.d);
}</span>

这样,你需要承受operator*返回值的构造和析构成本,但是从长远来看,那只是为了获得正确的行为而付出的一个小小的代价。但万一代价比较恐怖,承受不起,这是别忘了C++和所有编程语言一样,允许编译器实现者施行最优化,用以改善产出代码的效率,却不改变其可观察的行为。如果编译器施行优化,你的程序将保持它们该有的行为,但运行起来比预期更快。

以上可以总结为:在返回一个reference和返回一个object之间抉择时,挑出行为正确的那个。让编译器厂商为你尽可能降低成本吧!


《Effective C++》 读书笔记(详细总结55条款)上篇 下面从四个方面来说 (1) C++ 以 C 为基础,很多语句语法都没有改变,只是进行了扩展,C++ 解决问题的时候,可以理解为 C 的高效解法 (2) 面向对象,有构造函数,析构函数,继承、封装、多态、虚函数动态绑定,这是不同于C 语言的部分 (3) 泛型编程、可以理解为代码的模型,但是代码本身并不占用内存空间,只有在实例化的时候,才分配内存,所以可以提高代码复用性 阅读详情

相关推荐

Effective C++记录(精简版)

1、尽可能延后变量定义式的出现,尽可能在初始化的时候给初值实参,这样做可增加程序的清晰度并改善程序效率。1、绝对不要重新定义继承而来的non-virtual函数。会打破public继承时is a的关系。1、绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定(使用指针类型函数默认参数),而virtual函数——你唯一应该覆写的东西却是动态绑定。

qq_42974718的博客 1102

Effective C++条款21:必须返回对象时,别妄想返回其reference

Effective C++条款21:必须返回对象时,别妄想返回其reference(Don't try to return a reference when you must return an object)

CltCj的博客 808

Effective C++ 总结

当变量定义出现时,程序需要承受其构造成本;当变量离开其作用域时,程序需要承受其析构成本。因此,避免不必要的变量定义,以及延后变量定义式直到你确实需要它。// 效率低// 效率高Widget w;i < n;++i) {w = 取决于 i 的某个值;...这种做法产生的开销:1 个构造函数 + 1 个析构函数 + n 个赋值操作i < n;++i) {Widget w(取决于 i 的某个值);...这种做法产生的开销:n 个构造函数 + n 个析构函数。

qq_31978997的博客 369

《Effective C++》笔记 条款21-条款30

尽可能延后定义式的出现。这样做可以增加程序的清晰度并改善程序效率。

weixin_43931425的博客 256

避坑指南:Qt5.14.2 ARM交叉编译中那些你可能遇到的‘坑’及解决方案

本文深度解析了Qt 5.14.2在ARM架构上进行交叉编译的完整流程与常见难题。重点阐述了如何精准匹配交叉编译器与目标板环境、正确配置sysroot以及使用configure参数的艺术,并针对编译过程中的头文件缺失、链接错误等典型“坑”提供了经过验证的解决方案,旨在帮助开发者成功构建适用于嵌入式设备的Qt运行时库。

weixin_29279101的博客 353

【PLL】分频器设计权衡:从CMOS到CML的电路实现与优化

本文深入探讨了PLL中分频器的设计权衡,比较了CMOS和CML两种电路实现的优缺点。通过实际案例分析,展示了如何在速度、功耗和噪声之间取得平衡,特别是在5G射频芯片和Wi-Fi 6E等高频应用中的优化实践。文章还提供了混合架构设计和电源噪声协同优化的实用技巧,适合电路设计工程师参考。

weixin_42521843的博客 442

Effective C++之条款21:必须返回对象时,别妄想返回其reference

声明: 文中内容收集整理自《Effective C++(中文版)第三版》,版权归原书所有。 本内容在作者现有能力的基础上有所删减,另加入部分作者自己的理解,有纰漏之处敬请指正。 条款21:必须返回对象时,别妄想返回其reference Don't try to return a reference when you must return an object. 直接进入正题。 考虑一个有...

HakunaMatata的博客 275

极空间NAS小白也能搞定的Gitea部署:Docker+MySQL避坑全记录

本文为极空间NAS用户提供了一份详尽的Gitea私有代码仓库部署指南。针对极空间ARM架构和图形化Docker管理的特殊性,文章重点讲解了如何通过Docker和MySQL容器进行部署,并提供了三种获取兼容镜像的实战策略,特别是利用第三方镜像站转录的方法,帮助小白用户成功搭建并初始化Gitea服务,实现私有Git托管。

weixin_28318011的博客 368

《Effective C++》学习总结(条款16- 20)

条款16:成对使用 new 和 delete 时要采取相同形式 1.new:当使用new的时候,会发生如下两件事: 首先,一块内存会被分配出来; 其次,针对此内存会有一个(或更多)构造函数被调用。 2.delete:当使用delete的时候,会发生如下两件事: 首先,针对此内存会有一个(或更多)析构函数被调用; 其次,内存被释放(通过operator delete的函数)。 3.那么问题在于:即将被删除的那个指针,所指的是单一对象还是对象数组? 也就是:即将被删除的内存之内究竟存有多少对象?

无限进步 278

Ubuntu22.04中通过conda快速部署PyTorch开发环境(CPU/GPU双版本实战)

本文详细介绍了在Ubuntu22.04系统中,如何利用Conda高效部署PyTorch开发环境。文章分别提供了CPU与GPU双版本的实战教程,涵盖环境创建、镜像加速、版本匹配及深度验证等关键步骤,旨在帮助开发者快速搭建稳定、兼容的深度学习环境,从而专注于模型开发。

rose2的博客 556

sentinel——SLC数据下载、DEM数据下载拼接、精密轨道数据下载方法

Sentinel——SLC数据下载、DEM数据下载、精密轨道数据下载方法 SLC数据下载 网址:https://search.asf.alaska.edu DEM数据下载 1 手动下载 http://srtm.csi.cgiar.org/srtmdata/ 3.在SARscape中使用下载好的DEM 将下载好的DEM放在软件默认下载DEM路径下(默认路径是C:\Users<用户名&g...

u011322358的博客 2万+

《Effective C++》条款20

除此原因以外还有什么情况下建议使用pass-by-reference-to-const?宁以pass-by-reference-to-const替换pass-by-value。宁以pass-by-reference-to-const替换pass-by-value。2.因为存在继承关系,所以每次构造一个B类,就会多出来4个string对象。这段代码用了两个不同参数的getvalue函数,结果也不一样!所以得出结论:想要传递派生类对象的信息,需要使用。这样明显不是最佳效率的调用方法。还可以避免对象切割问题!

qq_64863535的博客 155

MATLAB - 使用 RRT 进行挖掘机运动规划

本例演示了如何使用运动规划器在包含障碍物的环境中为挖掘机规划路径。在此示例中,您将以运动树的简化形式为挖掘机建模,然后使用基于采样的运动规划器确定挖掘机在存在障碍物的两个姿势之间的可行路径。在 Simscape™ 多体™ 模型中模拟结果。

机器人控制与仿真应用 1850

EffectiveC++核心要点:设计、资源与安全

好的,以下是对《Effective C++》第二部分(通常涵盖条款18至27)的核心要点总结,重点关注。这些实践能显著提升代码的健壮性、可维护性和性能。建议结合具体项目场景应用这些原则。

2502_93949526的博客 731

UG二次开发GRIP刻字

UG二次开发GRIP刻字

qq285503851的博客 1138

4、机器人建模、规划与控制及运动学知识解析

本博客深入解析了机器人建模、规划与控制的核心内容,涵盖运动学与动力学的基本原理,并结合典型应用场景探讨了移动机器人与操作器的控制策略。同时,博客还介绍了机器人技术的发展趋势及其在工业和服务领域的广泛应用。

desk3的博客 124

【C++】万字详解Effective C++

本文为前段时间读《Effective C++》(侯捷译)时做的个人笔记。

Friday 4291

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Effective C++ 条款总结

Effective C++ 条款总结一、让自己习惯C++1 视C++为一个语言联邦2 尽量使用const等替换#define3 尽可能使用const4 确定对象使用前被初始化 自己在看这本书的时候,回去翻看目录的时候,有些规则会被遗忘,因此做个简单的小总结供自己和其他人参考,没读过的还是要先去读一遍的 一、让自己习惯C++ 1 视C++为一个语言联邦 C++是一种包含许多特性的语言,因而不要把它视为一个单一语言。理解C++至少需要学习一下4个部分: C语言。C++仍以C为基础 objected-ori

catkin_ws的博客 1177

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

C++ | 一文整理Effective C++ 55条款内容(全)

前言 文章目录前言1. 视C++为一个语言联邦2. const、enum、inline3.尽可能使用const4. 确定对象初始化】5. 了解C++默默编写并调用那些函数6. 不自动生成的函数,就明确拒绝7. 多态基类声明virtual析构函数8. 别让异常逃离析构函数9. 不在构造析构过程中调用virtual函数10. operator=返回reference to *this11. operator=处理自我赋值12. 赋值对象勿忘其每一个成分13. 以对象管理资源14. 资源管理类中小心coping

weixin_45926547的博客 5587

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++》笔记(1-4章,条款1-25)

2025.7.15 对Effective C++的个人笔记,主要记录本人觉得有必要记录的,部分更新了现代C++

Coolbike的博客 1122

【effective c++】条款十八:让接口容易被正确使用,不易被误用

摘要: Effective C++条款18强调设计接口时应使其易于正确使用且难以误用。关键策略包括:1) 促进正确使用:保持接口一致性(如STL的.size()),兼容内置类型行为;2) 防止误用:通过建立包装类型(如Month/Day)、限制操作(const返回值)、约束取值范围(静态工厂函数)等利用类型系统捕获错误;3) 资源管理:使用智能指针(如shared_ptr)转移责任,支持定制删除器解决跨DLL释放和互斥锁管理。现代C++的enum class、explicit和概念(Concepts)进一步

maybe_Tian_Xia的博客 739

Effective Modern C++ 条款39:一次事件通信的优雅解决方案

本文探讨了现代C++中线程间一次性事件通信的优雅解决方案。传统方法如条件变量存在锁开销和时序问题,轮询标志浪费CPU资源,而混合方案又过于复杂。文章提出使用std::promise<void>和std::future<void>的无数据信号传递机制,兼具简洁性和高效性,避免了虚假唤醒和忙等待问题。通过线程池延迟初始化等案例展示了实际应用,并提供了针对AI编程(Vibe Coding)的优化提示词策略。文章还扩展了多接收者场景和异常处理方案,为现代C++并发编程提供了一套资源高效、安全

软件科学-郝学胜 991

effective c++ 条款18 19 20 21

条款18:让接口容易被使用,不易被误用 在设计之前需要了解到用户可能会犯什么样的错误。原书中举例,设计一个表现日期的class class Date{ public: Date(int month, int day, int year); //... }; 这个最初的接口看起来没什么毛病但是,用户可能在输入的时候弄错次序并且可能会输入错误的不符合实际情况的日期格式。 可以...

weixin_42427338的博客 295

Effective C++改善程序与设计的55个条款

Cpper分为两种,一种是读过《Effective C++》,另一种是没读过。让我们学习“专家的经验累积”。为啥我比别人踩过的坑更少,那个因为我走的是前人铺平的道路

Lucky_Cpper的博客 1050

《Effective C++》核心思想与实践指南:从资源管理到现代C++编程

在C++编程中,资源管理和对象生命周期控制是构建健壮、高效软件的核心基础。其背后的关键原理是RAII(Resource Acquisition Is Initialization),它通过对象的构造与析构自动管理资源(如内存、文件句柄),从而从根本上避免资源泄漏。这一思想直接催生了现代C++中智能指针(如std::unique_ptr和std::shared_ptr)的广泛应用,它们通过自动化内存管理,显著提升了代码的安全性与可维护性。理解编译器默默生成的函数(如拷贝控制成员)和正确设计类的拷贝、移动语义,

weixin_33882452的博客 395
上一篇: 《Effective C++》设计与声明:条款18-条款19
下一篇: hihocoder最长回文子串
KangRoger
博客等级 码龄14年 728粉丝 280原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值