《Effective C++》读书笔记 5.实现

实现

条款26:尽可能延后变量定义式的出现时间

只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序控制流到达这个变量定义式时,你便得承受构造成本;当这个变量离开其作用域时,你便得承受析构成本。即使这个变量最终并未使用,仍需耗费这些成本,所以你应该尽可能避免这种情形

对于循环而言变量的定义方式通常有两种

//方法A:定义于循环外
Widget w;
for(int i=0;i<n;i++)
{
	w=....;
	...
}
//方法B:定义于循环内
for(int i=0;i<n;i++)
{
	Widget w(...);
	...
}

成本如下
做法A:1个构造函数+1个析构函数+n个赋值操作
做法B:n个构造函数+n个析构函数

如果classes的一个赋值成本低于一组构造+析构成本,做法A大体而言比较高效。尤其当n很大的时候。否则做法B比较好。此外,做法A名称w的作用域比做法B大,有时对程序的可理解性和易维护性造成冲突


条款27:尽量少做转型动作

C++提供四种新式转型:

const_cast<T>(expression)

通常被用来将对象的常量性移除,它也是唯一有此能力的转型操作符

dynamic_cast<T>(expression)

主将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理, 即会作出一定的判断。
若对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
若对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。
是唯一可能耗费重大运行成本的转型动作
详细用法

reinterpret_cast<T>(expression)

意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如将一个pointer to int转型为一个int。这一类转型在低级代码以外很少见

static_cast<T>(expression)

用来强迫隐式转换,例如int转为double,pointer-to-base转为pointer-to-derived。
static_cast与返回值的函数类似,返回的是一个临时副本,因此除非转为指针和引用,否则不要对转换后的对象进行赋值操作.

单个对象可能拥有一个以上的地址(例如以Base指向它时的地址和以Derived指向它时的地址)。这是C++可能会发生的,而实际上,一旦使用多重继承,这种事几乎以致发生。这是因为,转型的时候会存在一个偏移量,这个量与对象的布局方式和它们的地址计算方式有关,而这两样东西又和编译器相关

dynamic_cast通常运用场景:你想在一个你认定为派生类的对象上执行派生类的操作,但现在只有指向base的指针或引用。两个一般性做法可以避免这种情况:
1.使用容器(智能指针)直接指向派生类的指针
2. 在base class内提供virtual函数做你想对各个派生类做的事


条款28:避免返回handles指向对象内部成分

handle:reference、指针和迭代器统称handles

成员变量的封装性最多只等于“返回其reference的函数的访问级别”

如果const成员函数传出一个reference,后者所指数据和对象自身有关联,而它又被存储在对象外,那么这个函数的调用者可以修改那个数据

除了数据,private或者protected的函数也属于内部信息,不要返回它们的handles

dangling handles:返回了handles,但因为某些情况(函数返回值自动销毁),所指对象不存在

避免返回handles指向对象内部。遵守这个条款可增加封装性,帮助const成员函数行为像个const,并降低dangling handles可能性


条款29:为"异常安全"而努力是值得的

当异常被抛出时,"异常安全"函数有两个条件:

不泄露任何资源:从堆中申请的资源应该确保被释放

不允许数据败坏:函数不能对数据修改到一半而抛出异常以致数据被破坏.

解决"不泄露任何资源"很容易,只要使用资源管理类(如shared_ptr,见条款13)即可,"不允许数据败坏"是主要考虑的问题.

异常安全函数即使发生异常也不会泄露资源或者允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型

基本型:如果异常被抛出,程序内的任何事物仍保持在有效状态下。没有任何对象或数据结构会因此被破坏,但程序的当前状态不可预料

强烈型:如果异常被抛出,程序状态不改变。调用这样的函数,若函数失败,则程序恢复到调用函数之前的状态

不抛异常型:承诺绝不抛出异常

要实现1中所提出的两个条件,一般有以下策略:

  1. 智能指针的使用以确保不泄露堆中资源.

  2. 对函数语句顺序的细致规划以阻止数据的败坏,这未必能完全避免数据败坏.

  3. 使用"copy and swap"策略:为打算修改的对象做出一份副本,然后在副本上进行修改,若函数抛出异常,只有副本的数据发生败坏,若修改成功执行,调用swap函数进行置换.这是解决数据败坏的有效途径.

注意:途径3)虽然有效,但是仍然有以下限制:

1)使用"copy and swap"策略构造临时对象,因此要付出额外的资源和效率负担.

2)要使用swap函数,必须保证swap函数不抛出任何异常(见条款25).

3)使用"copy and swap"策略并不能彻底根除数据败坏的可能性,如果函数内调用其它函数,会产生"连带影响".

函数提供的“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全保证”的最弱者

异常安全性是指,在异常被抛出时满足:不泄露任何资源;不允许数据败坏

对于泄露资源,采用智能指针解决

不要为了表示某件事情发生而改变对象状态,除非它真的发生了

写代码的时候,需要对函数考虑异常安全性,首先通过智能指针防止资源泄漏,其次根据情况选择所需的安全性级别,应该挑选“现实可实施”条件下的最强烈等级,一般来说,实现不抛掷保证不太现实:
1 .如果程序要保证nothrow,那么就要保证它所调用的函数也nothrow
2.任何使用动态内存的代码(如STL容器)都有可能抛出无法找到足够内存而产生的ban_alloc异常,因此,提供异常安全保证通常从基本保证和强烈保证中选择.


条款30:透彻了解inlining的里里外外

将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序速度提升机会最大化

inline函数通常被置于头文件内,因为大多数环境在编译过程中进行inlining

将函数定义于class定义式内将被隐喻声明为inline(包括友元函数)

inlining好处:不需受函数调用带来的额外开销;编译器最优化机制通常被设计用来浓缩“不含函数调用”的代码,inline某个函数,或许编译器有能力对它的执行语境相关最优化

inlining坏处:它的观念是“对此函数的每个调用”都用函数本体替代,在编译过程完成,将增加你的目标码大小;
如果函数库内有一个inline函数,一旦改变,所有用到该函数的客户端都必须重新编译。
有一个事实更加重要:“大部分调试器对inline函数束手无策”,毕竟无法在一个不存在的函数内设置断点。

不inline的情况:函数太过复杂;函数是virtual;通过函数指针进行调用的地方;构造函数和析构函数(即使是空的,但是C++内部会对这两个函数做基本的操作,本身就带有大量代码)

inline只是对编译器的一个申请,并不是强制命令,编译器对代码量复杂的函数(例如循环或递归),virtual函数(必须在执行期才能决定本体)拒绝展开(并会产生警告).


条款31:将文件间的编译依存关系降低至最低

支持“编译依存性最小化”的一般构想:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes 和 Interface classes

程序库头文件应该以“完全且仅有声明式”的形式存在,只有三个例外:类定义,inline函数,const变量。这种做法不论是否涉及templates都适用

一般写代码,头文件中包含其他的头文件,当其中一个头文件改变的时候,包含它的所有文件全部都要重新编译,也就是编译依存关系较强

所谓相依于声明式不是定义式,本质是让头文件尽可能自我满足。

2种手段:
– handle class:将实现的类分成两个类,一个(handle class)只有若干功能外壳和实际实现这些功能的类的指针;handle class的每个函数都调用对应的指针类的功能函数。这样实现和声明就分开了,当修改了某个头文件,只有handle的实现类需要重新编译(只有它include)

– Interface class:通过将具体实现放在派生类中,使用时include基类头文件,利用多态性通过操纵基类指针操纵派生类对象,因为只有派生类的实现文件include派生类的.h文件,因此对派生类做的任何改变只会影响到派生类的.h文件和.cpp文件.

– 缺陷:这两种手段都有不足。Handle class必须实现指针,每次调用函数还包含一次指针调用函数,增加访问的间接性;Interface class因为有virtual函数,除了增加内存(虚表),还增加了跳跃

这些手段会造成额外成本,当它们导致速度和大小差异过于重大以至于classes之间的耦合相形之下不成为关键时,就以具象类代替它们
更深入了解本节

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Effective C++ 中文版》是由Scott Meyers所编写的一本关于C++编程的指南。它不仅是C++编程者的必备书籍,也是所有编程人员的经典读物。本书在讲解C++编程过程中,给出了许多实用的技巧和建议,帮助程序员更好地使用C++编写高效、可靠的程序。 本书共包含50个条款,每个条款都包含一个具有实际意义的问题和解决方案。这些解决方案是作者多年编写C++代码的经验总结,结合了C++的最佳实践,旨在帮助读者更好地理解C++的语言特性和语言使用习惯。这些技巧从简单到复杂,由浅入深地介绍了如何编写高效的C++代码。 在本书中,你将学习到: 1. 内存管理技巧。包括如何使用智能指针、如何处理内存泄漏问题等。 2. 类设计和继承技巧。包括如何设计和实现抽象基类、虚函数等。 3. 异常处理和错误处理。包括如何处理异常、如何正确使用异常、如何进行错误处理等。 4. 代码优化和调试技巧。包括如何做好代码优化、如何进行调试、如何实现高效数据结构等。 《Effective C++ 中文版》不仅适用于初学者,也适合有一定经验的C++开发人员参考。它是一本非常实用的、能帮助你更好地理解和掌握C++编程技巧的书籍。无论你是一名编程新手还是有多年经验的专业开发人员,都值得一读。 ### 回答2: 《Effective C++ 中文版》是一本关于C++编程的经典书籍。其中包含了许多有关C++编程的实用技巧,以及注意事项和最佳实践。本书的作者Scott Meyers是一位著名的C++专家,通过本书他在编程技巧和C++语言语法方面给我们提供了很多宝贵的经验。本书主要分为50个章节,每一个章节有自己的主题,涉及面非常广泛,包括了一些比较基础的C++概念,以及一些高级的编程技巧和设计模式的应用。其中一些章节是非常值得注意的,比如说有关内存管理,函数重载以及类设计等等。 通过《Effective C++ 中文版》这本书,读者可以学到很多C++编程的实用技巧,这些技巧在实际开发中非常有用。例如,书中介绍了如何正确使用函数重载,如何避免内存泄漏以及如何正确地设计类等等。此外,这本书对于大家掌握C++的语法和一些编程技巧以及习惯养成等方面也是非常有帮助的。因此,《Effective C++ 中文版》是一本非常值得阅读的C++经典之作,对于C++初学者和专业程序员都是有益的。 ### 回答3: 《Effective C》是一本关于C语言编程的实用指南,旨在帮助读者更加高效地使用C语言进行开发。本书作者为Scott Meyers,是知名的C++编程之父,在本书中他分享了自己多年来在C语言领域的经验和技巧。 本书主要分为四个部分,分别是基础知识、指针、内存管理和高级技巧。第一部分主要介绍C语言的基本语法和特性,包括编码风格、数据类型、流程控制等;第二部分则深入讲解指针的概念和用法,包括指针的语法、指针和数组、指针和函数等;第三部分主要涉及内存管理方面的知识,包括动态内存分配、内存泄漏、内存地址等;第四部分则介绍一些高级技巧,如位操作、函数指针、字符串操作等。 本书着重强调了C语言的“低级别”特性,如指针和内存管理,这正是C语言的优越之处。但同时,这些特性也是导致C语言存在一些常见问题的根源,如内存泄漏、野指针等。因此,本书特别注重这些问题的防范和解决方法,为读者提供了很多实用的技巧和建议。 总之,《Effective C》是一本非常实用和有价值的C语言编程指南。无论是新手还是经验丰富的程序员,都可以从本书中学到很多东西,提升自己的编程水平和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值