Effective C++.docx
- 文档编号:26132851
- 上传时间:2023-06-17
- 格式:DOCX
- 页数:23
- 大小:31.12KB
Effective C++.docx
《Effective C++.docx》由会员分享,可在线阅读,更多相关《Effective C++.docx(23页珍藏版)》请在冰豆网上搜索。
EffectiveC++
1,C++属于一个语言联邦:
C Object-OrientedC++ TemplateC++ STL
2,应尽量以const,enum,inline替换#define
如果一个常量是class专属常量又是static,且为整数类型(int,char,bool),则需特殊处理:
只要不取它们的地址,可以声明并使用它们而不须提供定义式。
但是如果取某个class专属常量的地址,或纵使不取其地址而编译器却(不正确的)坚持要看到一个定义式,必须提供定义式
enum的行为某方面比较像#define而不像const:
取一个const地址合法,但是取一个enum地址就不合法,而取#define也不合法,如果不想让被人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束
#define和enum不会设定额外的空间,const在优秀的编译器中也许可能也是这样,但是不够优秀的编译器就必须设定额外的空间
3,尽可能使用const
const语法虽然变化多端,但并不莫测高深。
如果关键字const出现在*左侧,表示被指物是常量;如果出现在*右边,表示指针自身是常量;如果出现在*两边,表示被指物和指针都是常量
std:
:
vector
conststd:
:
vector
:
interator iter=vec.begin() T*const
std:
:
vector
const_iteratoriter=vec.begin() constT*
将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上
const特性的两个函数可以重载(必须在类中,不在类中会出现问题)const函数不可改变对象任何变量(注意区分指针的情况,只要指针不变,指向的值可变化)
注意操作符[]的重载要返回值的引用,否则不能对结果赋值(相当于对值的一个拷贝赋值,没有意义)
mutable可以在常函数中修改其值,但是一般作用是:
1,用于缓存2,或必须在常函数中修改值
利用constoperator[]实现出non-const版本:
classTextBlock{
public:
constchar&operator[](std:
:
size_tposition)const{ ..... returntext[position];}
char&operator[](std:
:
size_tposition){
return const_cast
};
非const调用const是安全的
const调用非const是不安全的,(因为非const有可能改变)
总结:
1>将某些东西声明为const可帮助编译器侦测出错误用法。
const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体
2>编译器强制实施bitwiseconstness,但你编写程序时应该使用“概念上的常量性”(conceptualconstness)
3>当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复
4,确定对象被使用之前已被初始化:
为了保证一定性,使用内建数据类型一定要初始化,构造对象要保证构造函数初始化它的成员
C++对“定义在不同的编译单元内的non-local static对象”的初始化相对次序并无明确定义。
因为决定它们初始化次序相当困难,根本无解
最常用的形式,就是 多个编译单元内的non-localstatic对象经由“模板隐式具体化,implicittemplateinstantiations"形式(而后者自己可能也是经由”模板隐式具现化“形成)
任何一种non-conststatic对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。
处理方法:
在程序的单线程启动阶段手工调用所有的reference-returning函数,这可消除与初始化有关的“竞速形势”
为了避免在对象初始化之前过早地使用它们,你需要做三件事:
第一,手工初始化内置型non-member对象。
第二,使用成员初值列对付对象的所有成分
第三,在”初始化次序不缺定性“(这对不同编译单元所定义的non-localstatic对象是一种折磨)氛围下加强设计
总结:
1>为内置对象进行手工初始化,因为C++不保证初始化它们
2>构造函数最好使用成员初始化列表,而不要在构造函数体内使用赋值操作。
次序和变量声明的顺序一致
3>为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-localstatic对象
5,编译器默认提供四个public且inline函数,赋值,拷贝,构造,析构,但是它们是在需要的时候才被编译器创建出来的
C++不允许让引用改指向不同的对象
>如果打算在一个“内含reference成员”的class内支持赋值操作,必须自己定义copyassignment操作符
>如果某个baseclass将copyassignment操作符声明为private,编译器将拒绝为其derivedclasses生成copyassignment操作符
6,若不想使用编译器自动生成的函数,就该明确拒绝:
将他们声明为private,同时不要实现他们,这样的话即使friend或者成员函数调用他们连接器会提示错误
另外一种方法是把他们声明为private在一个单独的类中,然后用这个类继承它
7,为多态基类声明virtual析构函数
>带有多态性质的baseclasses应该声明一个virtual析构函数。
如果class带有任何virtual函数,他就应该拥有一个virtual析构函数
>classes的设计目的如果不是作为baseclasses使用,或不是为了具备多态性,就不该声明virtual析构函数
8,别让异常逃离析构函数
>析构函数绝对不要吐出异常。
如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序
>如果客户需要对某个操作函数运行期间抛出异常作出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作
9,绝不在构造和析构过程中调用virtual函数
>无法使用virtual函数从baseclasses向下调用,在构造期间,可以藉由“令derivedclasses将必要的构造信息向上传递到baseclass构造函数”替换加以弥补
>在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derivedclass(比起当前执行构造函数和析构函数的那层)
10,令operator=返回一个referenceto*this
目的是:
为了实现“连锁赋值”
11,在operator=中处理“自我赋值”
自我赋值 发生在对象被赋值给自己时
>确保当对象自我赋值时operator=有良好行为。
其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap
>确定任何函数如果操作一个以上的对象,而其中多个对象是同一对象时,其行为仍然正确
12,复制对象时勿忘其每一个成分
自己编写copying函数,要确保:
1,复制所有local成员变量
2,调用所有baseclasses内的适当copying函数
>Copying函数应该确保复制“对象内的所有成员变量”及“所有baseclass成分”
>不要尝试以某个copying函数实现另一个copying函数。
应该将共同机能放进第三个函数中,并由两个copying函数共同调用
所谓资源就是:
一旦用了它,将来必须还给系统,否则会出现问题。
C++最常用的资源就是动态内存分配,当然还有其他资源:
文件描述扶,互斥锁,图形界面中的字型和笔刷、数据库连接、以及网络sockets
13,以对象管理资源
两个关键想法:
>获得资源后立刻放进管理对象内
>管理对象运用析构函数确保资源被释放
由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一对象。
如果真是那样,对象会被删除一次以上,而那会使你的程序搭上驶向“未定义行为”的快速列车上。
为了预防这个问题,auto_ptr有一个不寻常的性质:
若通过copy构造函数或copyassignment操作符复制它们,它们会变成null,而复制所得的指针将取的资源的唯一控制权!
!
!
(该性质有利也有弊)
auto_ptr的替代方案是“引用计数型智慧指针”(RCSP),RCSP也是个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。
RCSP提供的行为类似垃圾回收,不同的是RCSP无法打破环状引用:
:
:
tr1:
shared_ptr就是一个RCSP
注意:
auto_ptr和tr1:
:
shared_ptr两者都在析构函数内做delete而不是delete[]动作!
!
如果对数组指针调用则是馊主意
>为了防止资源泄漏,请使用RAII对象,它们在构造函数中活得资源并在析构函数中释放资源(ResourceAcquisitionIsInitialization,RAII)
>两个常被使用的RAII classes分别是tr1:
:
shared_ptr和auto_ptr。
前者通常是较佳选择,已经其copy行为比较直观。
若选择auto_ptr,复制动作会时它(被复制物)指向null
14,在资源管理类中小心copying行为
在RAII中当一个对象被复制时,出现严重后果,一般有两种方案:
>禁止复制(声明为private)
>对底层资源祭出“引用计数法”(reference-count) 这种做法一般如果内含有一个tr1:
:
shared_ptr,但是默认事件是count为0时删除,幸运的是我们可以自己指定为0时的事件
类的析构函数会自动调用其non-static成员变量的析构函数
复制底部资源 (深度拷贝)
转移底部资源的拥有权 (auto_ptr)
15,在资源管理类中提供对原始资源的访问
tr1:
:
shared_ptr和auto_ptr都提供了一个get函数,用来执行显式转换,也就是它返回智能指针内部的原始指针,以便于直接访问时可以通过
它们还重载了(->和*)操作符
>APIs往往要求访问原始资源(rawresources),所以每一个RAIIclass应该提供一个“取得其所管理之资源”的办法
>对原始资源的访问可能经由显式转换或隐式转换。
一般而言显式转换比较安全,但隐式转换对客户比较方便
16,成对使用new和delete时要采用相同形式
最好不要用typedef,否则会造成语义不清:
typedef std:
:
stringAddressLines[4];
std:
:
string *pal=newAddressLines;
deletepal; 错误!
!
!
delete[]pal; 正确!
!
!
17,以独立语句将newed对象置入智能指针
> 以独立语句将newed对象存储于(置入)智能指针内。
如果不这样做,一旦异常被抛出,有可能导致难以觉察的资源泄露
18,让接口容易被正确使用,不易被误用
>好的接口很容易被正确的使用,不容易被误用。
应该在所有的接口中努力达成这些性质
>“促进正确使用”的办法包括接口一致性,以及与内置类型的行为兼容
>“组织误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任
>tr1:
:
shared_ptr支持定制型删除器。
这个防范DLL问题,可被用来自动解除互斥锁等等
19,设计class犹如设计type
好的types是一项艰巨的工作。
好的types有自然的语法,直观的语义,以及一或多个高效的实现品
如何设计好的classes:
>新的type的对象应该如何被创建和销毁
>对象的初始化和对象的赋值该有什么差别
>新的type的对象如果被passedbyvalue(以值传递),意味着什么
>什么是新type的“合法值”
>新的type需要配合某个继承图系么
>新的type需要什么样的转换
>什么样的操作符和函数对此新type而言是合理的
>什么样的标准函数应该驳回
>谁该取用新type的成员
>什么是新type的“未声明接口”
>新type有多么一般化
>真的需要一个新type么
20,宁以pass-by-reference-to-const替换pass-by-value
>尽量以pass-by-reference-to-const替换pass-by-value。
前者通常比较高效,并可避免切割问题
>以上规则并不适用于内置类型,以及STL的迭代器和函数对象。
对它们而言,pass-by-value往往比较适当
21,必须返回对象时,别妄想返回其reference
任何时候看到一个reference声明式,都应该立刻问自己,它的另一个名称是什么?
因为它一定是某物的另一个名称
任何函数如果返回一个reference指向某个local对象,都将一败糊涂
>绝不要返回point或reference指向一个localstack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个localstatic对象而有可能同时需要多个这样的对象
22,将成员变量声明为private
>切记将成员函数声明为private。
这可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证,并提供class作者以充分的实现弹性
>protected并不比public更具封装性
23,宁以non-menber、non-friend替换menber函数
这样做可以增加封装性、包裹弹性和机能扩充性
24,若所有参数皆需要类型转换,请为此采用non-member函数
>如果需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
25,考虑一个不抛出异常的swap函数
pimpl手法 :
pointtoimplementation
C++只允许对classtemplate偏特化,在functiontemplates身上偏特化行不通,解决方案是:
为它添加一个重载版本
>当std:
:
swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
>如果你提供一个memberswap,也该提供一个non-memberswap用来调用前者。
对于classes(而非templates),也请特化std:
:
swap
>调用swap时应针对std:
:
swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”
>为“用户定义类型”进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西
26,尽可能延后变量定义式的出现时间
>尽可能延后变量定义式的出现。
这样做可增加程序的清晰度并改善程序的效率。
27,尽量少做转型动作
>const_cast通常被用来将对象的常量性转除。
它也是唯一有此能力的C++-style转型操作符
>dynamic_cast主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个对象。
它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作
>reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就是表示它不可移植。
例如将pointtoint转型为int
>static_cast用来强迫饮食转换。
例如将non-const转型const对象,或将int转型double等等。
但是无法将const转型non-const
任何一个类型转换(不论是通过转型操作而进行的显式转换,或通过编译器完成的隐式转换)往往真的令编译器编译出运行期执行的码
单一对象(例如Derived对象)可能拥有一个以上的地址(例如“以base*指向它”时的地址和“以Derived*指向它”时的地址)行为
之所以需要dynamic_cast,通常是因为想在一个认定为derived对象身上执行derivedclass操作函数,但手上却只有一个"指向base”的pointer或reference,只能靠它们呢来处理对象
绝对必须避免的一件事是所谓的"连串dynamic_casts"
>如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。
如果有个设计需要转型这个动作,试着发展无需转型的替代品
>如果转型是必要的,试着将它隐藏于某个函数背后。
客户随后可以调用该函数,而不需要将转型放进他们自己的代码内
>宁可使用C++-style转型,不要使用旧式转型。
前者容易辨识出来,而且也比较有着分门别类的职掌
28,避免返回handles指向对象内部成分
两个教训:
--->成员变量的封装性最多只等于"返回其reference"的函数的访问级别
--->如果const成员函数传出一个reference,后者所指数据与对象自身关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据
handles:
指针,reference,迭代器
避免返回handles(reference,pointer,iterator)指向对象内部。
遵守这个约定可增加封装性,帮助const成员函数的行为像个const,并将发生”虚吊号码牌“(danglinghandles)的可能性降到最低
29,为"异常安全"而努力是值得的
当异常被抛出时,带有异常安全性的函数会:
=>不泄露任何资源
=>不允许数据败坏
异常安全函数提供以下三个保证之一:
==>基本承诺
==>强烈保证
==>不抛掷保证
1,>异常安全函数(Exception-safefunctions)即使发生异常也不会泄露资源或允许任何数据结构败坏。
这样的函数区分三种可能的保证:
基本型,强烈型,不抛异常型
2,>“强烈保证"往往能够以copy-and-swap实现出来,但"强烈保证"并非对所有函数都可实现或具备实现意义
3,>函数提供的"异常安全保证"通常最高只等于其所调用之各个函数的"异常安全保证"中的最弱者
30,透彻了解inlining的里里外外
inline只是对编译器的一个申请,不是强制命令
inline函数通常一定被置于头文件内
inline在大多数C++程序中时编译器行为,只有少数基于.NETCLI(CommonLanguageInfrastructure,公共语言基础设施)可以在运行期完成inlining
Template通常也被置于头文件中,以为一旦被使用,编译器为了将它具现化,需要知道它长什么样子(像inline),(并不是同一准则,某些建置环境可以在连接期执行template具现化)
编译器拒绝将过于复杂的函数(循环或递归)的函数inlining。
而所有对virtual函数的调用(除非最平淡无奇的)也对会使inling落空,因为virtual是运行期确定
编译器通常不对“通过函数指针而进行的调用”实施inlining,因为要确定地址
inline函数无法升级
很多调试器对inline函数束手无策
>将大多数inlining限制在小型、被频繁调用的函数身上。
这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化
>不要只因为functiontemplates出现在头文件,就将它们声明为inline
31,将文件间的编译依存关系降至最低
如果使用objectreferences或objectpointers可以完成任务,就不要使用objects
如果能够,尽量以class声明替换class定义式
为声明式和定义式提供不同的头文件
>支持“编译依存性最小化”的一般构想是:
相依于声明式,不要相依于定义式。
基于此构想的两个手段是Handleclasses和Interfaceclasses
>程序库头文件应该以“完全且仅有声明式”的形式存在。
这种做法不论是否涉及templates都适用
继承与面向对象
32,确定public继承塑模出is-a关系
以C++进行面向对象编程,最要的一个原则:
公开继承意味"is-a“的关系
企鹅是一种鸟,但企鹅不会飞理论解决办法:
1,双继承 2,重新(一个错误的)飞功能 3,都不写飞功能
>"public"继承意味着is-a。
适用于baseclasses身上的每一件事情一定也适用于derivedclasses身上,因为每一个derivedclass对象也都是一个
baseclass对象
33,避免遮掩继承而来的名称
1#include
2usingnamespacestd;
3
4classBase{
5private:
6intx;
7public:
8virtualvoidmf1()=0;
9virtualvoidmf1(int);
10virtualvoidmf2();
11voidmf3();
12voidmf3(double);
13};
14
15classDerived:
publicBase{
16public:
17virtualvoidmf1();
18voidmf3();
19voidmf4();
20};
21
22intmain()
23{
24Derivedd;
25intx;
26
27d.mf1();//Derived:
:
mf1
28d.mf1(x);//ERROR!
!
!
NOARGSusingBase:
:
mf1
29d.mf2();//Base:
:
mf2
30d.mf3();//Derived:
:
mf3
31d.mf3(x
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Effective C+
![提示](https://static.bdocx.com/images/bang_tan.gif)