Effective C++快速复习

文章提供了C++编程的多个最佳实践,强调了将C++视为语言联邦,使用const和初始化来增强安全性,理解默认构造函数和拷贝行为,以及正确处理多态和虚拟函数。此外,重点介绍了资源管理,如RAII原则和智能指针的使用,以及异常安全的析构函数。模板和泛型编程方面,提倡使用成员函数模板和traits类。最后提到了定制new和delete的操作,以及重视编译器警告和利用标准库如TR1和Boost。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Effective C++快速复习

习惯 C++

01 视 C++ 为一个语言联邦:C、Object-Oriented C++、Template C++、STL

02 尽量以 const, enum, inline 替换 #define:其实是尽量以编译器替换预处理器比较好,因为 #define 只是简单的字符串匹配替换,编译器无法看到,不利于 debug,也容易出问题

03 尽可能使用 const:Rust 语言设计就是 const 作为默认语义,可以帮助编译器侦测出错误用法

04 确定对象被使用前已经被初始化:内置类型对象进行手动初始化,C++不保证初始化他们;尽量使用初始化列表来初始化

构造,析构,赋值

05 了解 C++ 默认编写了哪些函数:默认构造函数、默认拷贝构造函数、默认拷贝赋值操作符、默认移动赋值操作符、析构函数

06 可以明确拒绝编译器自动生成的函数:例如将相应函数声明为 private 且不予实现,例如 Boost 里边有个 uncopyable 类

07 为多态基类编写 virtual 函数:带有多态性质的基类应该声明 virtual 析构函数,或者说一个类只要带有 virtual 函数,就应该有 virtual 析构函数;

08 别让异常逃离析构函数:如果析构函数调用的函数可能抛出异常,那么析构函数就应该捕捉任何一场,不传播或者直接 abort ;如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么类应当提供一个普通函数,instead of 析构函数,执行该操作

09 绝不在构造和析构过程中调用 virtual 函数:你以为你调用的是子类的 virtual 函数,实际上可能是父类的 virtual 函数。在基类构造和析构期间调用的 virtual 函数不可下降至派生类。

*10 令 operator= 返回一个 this 引用:为了实现连续赋值

11 在 operator= 中处理自我赋值:比较来源对象和目标对象的地址是否相同;包括如果操作一个大的对象里有若干个对象,也要分别考虑是否有自我赋值。

12 复制对象时勿忘其每一个成分:拷贝赋值操作符要覆盖所有成员变量以及基类的成分(例如直接调用基类的拷贝赋值操作符);但是不要在拷贝赋值操作符里调用拷贝构造函数,而是要把二者的共同点提取出来作为一个新的函数

资源管理

13 以对象管理资源:使用 RAII 和智能指针的技术防止资源泄露

14 资源管理类中小心 coping 行为:常见的 RAII class copying 行为是:抑制复制、使用引用计数法、一同复制底部资源、转移底部资源的所有权

15 在资源管理类中提供对原始资源的访问:因为 APIs 往往要求访问原始资源(raw resources);可以采用显式转换和隐式转换的方式,前者比较安全,后者方便用户使用

16 成对使用 new 和 delete 时要采取相同形式:就是在有没有带数组 [] 符号上,new 和 delete 必须要统一

17 以独立语句将 new 得到的对象放入智能指针:意思是智能指针的以一个原始指针作为参数的构造函数是 explicit 的,所以要主动将 new 得到的对象指针放入智能指针里。

设计与声明

18 让接口容易被正确使用、不易被误用:接口一致、与内置类型行为兼容;消除客户的资源管理责任

19 设计 class 犹如设计 type:考虑新的类型应当如何被创建和销毁、初始化和赋值有着怎样的差别、值传递和引用传递有什么差别、是否需要配合某个继承体系、是否需要类型转换、如何使用、是否真的需要一个新的 class

20 宁以 pass-by-reference-to-cont 替换 pass-by-value:前者更高效,并且在继承关系存在时还可以防止子类部分被切割掉;但对于内置类型、STL迭代器和函数对象,后者更加合适。

21 必须返回对象时,不要返回其 reference:local stack 对象在函数结束时就会被销毁,如果是 heap-allocated 对象也应该返回 pointer 而不是 reference

22 将成员变量声明为 private:protedted 并不必 public 更具有封装性;将成员变量全部声明为 private,可赋予客户访问数据的一致性、进行更细粒度的访问控制、允许约束条件得到保证,给类的编写者更加充分的实现弹性

23 宁以 non-member、non-friend 替换 member 函数:这样可以提高封装性、包裹弹性和机能扩充性,可以将这类 non- 函数与类放在同一个命名空间下。(可以这样理解:增加这些 non- 函数只会在原有的封装性下做事情,而增加 member 函数却有可能会暴露出新的访问权限)

24 若所有参数皆需类型转化,请为此采用 non-member 函数:令 class 支持隐式类型转换往往是个坏主意;例如设计一个新的数值类型,它的算数运算想要兼容原有的内置类型,就需要将这些 operator+ 等函数写成 non-member;

25 考虑写一个不抛出异常的 swap 函数:自己提供的 swap 成员函数效率更高;定义完一个 member swap,也应当提供一个 non-member swap(即特化 std::swap()),来方便使用;为用户定义的类型进行 std templates 全特化是好的,但是不要往 std 命名空间里加入全新的东西。

实现

26 尽可能延后变量定义出现的时间:要用了再来定义,而不要像一些老的代码一样在开头定义好所有需要用到的变量;这样程序更加清晰,而且效率也可能得到提升。

27 尽量少做 casting 动作:即使要类型转换,也用 C++ 的新式类型转换(static_cast、dynamic_cast、const_cast、reinterprete_cast),这样在代码中容易被识别,也方便编译器诊断出错误;注重效率的代码中尽量不要用 dynamic_cast;注意 reinterprete_cast 不可移植;

28 避免返回 handles 指向对象内部成分:handles 指 references、指针、迭代器等,这样可以增加封装性

29 注意异常安全:注意不泄露任何资源、不允许数据破坏

30 透彻了解 inlining 的里里外外:inline 更适合小型的、被频繁调用的函数上,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

31 将文件键的编译依存关系降至最低:能使用 object reference 或者 pointer 弯沉任务的,就不要使用 object;尽量用 class 声明式替换定义式;为声明和定义提供不同的源文件,头文件应当完全仅有声明式。

继承与面型对象设计

32 public 继承应当塑模出 is-a 关系:is-a 关系是说,子类是一种基类,比如苹果是一种水果

33 避免遮掩继承而来的名称:可以用 using 声明或者 forwarding functions 的技巧来使用父类中被遮掩的名称

34 区分接口继承和实现继承:成员函数的接口总是会被继承;

  • 声明 pure virtual 函数是为了让派生类只继承函数接口、而必须要自己实现这个函数;
  • 声明 impure virtual 函数的目的,是让派生类继承该函数接口,同时提供一个默认实现版本
  • 声明 non-virtual 函数是为了让派生类直接继承函数接口,并且提供强制性实现

35 考虑 virtual 函数以外的其他选择:例如可以用 non-vritual interface 实现 Template Method 模式、Function Pointer 或 std::function 实现 Stategy 模式、

36 绝不重新定义继承而来的 non-virtual 函数:正如 34 中所讲,这是为了提供一份强制性的实现。

37 绝不重新定义继承而来的缺省参数值:缺省参数值是静态绑定,如果派生类继承了一个带有缺省参数值的函数,那么一定不要改缺省参数值(可以直接不写)

38 通过复合塑模出 has-a 或 is-implemented-by:复合是指 composition

39 审慎地使用 private 继承:private 继承也是 is-implemented-by 的意思,比复合的级别更低,可以实现 EBO;

40 审慎地使用多重继承:多重继承可能会带来歧义,而且 virtual 继承会带来 overhead,当 virtual base class 不带任何数据,将是最具实用价值的情况。当然多重继承也有正当用途,例如 public 继承某个 interface class 的同时 private 继承某个协助实现的 class

模板与泛型编程

41 了解隐式接口和编译器多态:对于 template 而言,接口是隐式的,基于有效表达式,多态则是通过template 具现化和函数重载解析发生于编译期。

42 了解 typename 的双重意义:既可以用在模板参数声明时,和 class 一样的用法,又可以用在表示由模板参数得到的派生类型上,标识嵌套从属类型名称,告诉编译器这是个类型。

43 学习处理模板化基类内的名称:在 derived class template 内通过 this-> 来引用 base class template 内的成员名称

44 将与参数无关的代码抽离 templates:因非类型模板参数而造成的代码膨胀,往往可以通过以函数参数或者 class 成员变量替换 template 参数来消除;

45 运用成员函数模板接受所有兼容类型:如果声明 member templates 用于泛化拷贝构造动作或泛化赋值操作,还是需要声明正常的拷贝构造函数和赋值操作符

46 需要类型转换时请为模板定义非成员函数:当我们编写一个 class template,而它所提供的与此template相关的函数支持所有参数的隐式类型转换时,请将哪些函数定义为 class template 内部的 friend 函数(例如前面提到的定义一个新的数值类型)

47 使用 traits classes 表现类型信息:通过萃取其可以是类型相关信息在编译器可用,参考 STL 的 iterator 实现

48 认识模板元编程:是一种编程范式,可将工作从运行期移往编译器,更早实现错误侦测,具有更高的执行效率

定制 new 和 delete

49 了解 new-handler 的行为

  • 需要让更多内存可以被使用

  • 当前 new-handler 无法获取更多内存时,可以安装别的 new-handler 替换自己

  • 卸除 new-handler

  • 抛出 (派生自)bad_alloc 的异常

  • 不返回:通常调用 abort 或 exit

50 了解替换编译器的 new 和 delete 的合理时机:往往是为了获得非传统的行为

  • 用来检测运用上的错误:例如 new 成功而 delete 失败造成的内存泄漏
  • 强化效能:编译器提供的 new 和 delete 不适用于长时间执行的程序
  • 收集统计数据:用于调试、收集 heap 使用信息

51 编写 new 和 delete 时需固守常规

  • operator new 内应当含有一个无穷循环,并且不断尝试分配内存;如果无法满足内存需求,就应当调用 new-handler;能够处理 0B 的申请;
  • operator delete 在接受 null 指针时不要做任何事情;

52 写了 palcement new 也要写 placement delete

杂项讨论

53 不要轻忽编译器的警告:0 error 不是最终目标

54/55 熟悉 TR1、Boost 等标准程序库 :虽然很多东西现在 C++11 以后都有了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Air浩瀚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值