Effective C++Effective C++
🩸

Effective C++

1、Federation of languages 语言联合体2、consts enums inlines 取代#defines3、尽量用const4、确保对象被初始化5、了解C++为你偷偷加上和调用什么函数6、如果不想用compiler-generated functions(编译器生成函数 item5)就明确拒绝7、基类的destructors函数声明为virtual8、防止因为exceptions而离开destructors9、不要在construction和destruction调用虚函数10、assignmen operators返回一个reference to *this11、在operator=处理assignment to self12、拷贝一个对象的所有组成部分13、使用对象来管理资源14、谨慎考虑资源管理类的拷贝行为15、在资源管理类中访问裸资源(raw resources)16、new delete 、new [] delete [] 匹配使用17、在一个独立语句构建智能指针18、是接口易于正确使用,难以错误使用19、视类设计为类型设计20、pass-by-reference-to-const replace pass-by-value21、返回对象不用引用22、数据成员声明为private23、用非成员非友元函数取代成员函数24、当类型转换应用与所有参数时,声明为非成员函数25、考虑支持不抛异常的swap26、只要有可能就推迟变量定义27、将强制转型减到最少28、避免返回对象内部构件的“句柄”29、争取异常安全(exception-safe)的代码30、理解inline化的介入和排除31、最小化文件之间的编译依赖32、✳确保public inheritance模拟“is-a"33、避免覆盖(hiding)继承得到的名字34、区分inheritance of interface(接口继承)和inheritance of implementation(实现继承)35、考虑可选的virtual fuctions(虚拟函数)的替代方法36、绝对不要重定义一个inherited non-virtual function(通过继承得到的非虚函数)37、绝不要重定义一个函数的inherited default parameter value(通过继承得到的缺省参数值);不要在虚函数上加默认参数38、通过composition(复合)模拟“has-a”(有一个)或“is-implemented-in-terms-of”(是根据...实现的)39、谨慎使用private inheritance(私有继承)40、谨慎使用multiple inheritance(多继承)41、理解implicit interfaces(隐式接口)和compile-time polymorphism(编译器多态)42、理解typename的两个含义43、了解如何访问templatized base classes(模板化基类)中的名字44、从templates中分离出parameter-independent(参数无关)的代码45、用member function templates(成员函数模板)接受all compatible types(所有兼容类型)46、需要type conversion(类型转换)时在templates内定义non-member functions47、为类型信息使用traits classes(特征类)48、感受template metaprogramming(模板元编程)49、了解new-handler的行为50、领会何时替换new和delete才有意义51、编写new和delete时要遵守惯例

1、Federation of languages 语言联合体

  • C with classes
  • Object-Oriented C++
  • Template C++
  • STL
 

2、consts enums inlines 取代#defines

#define xxx xxx

const int xxx=xxx;
const int* const xxx=xxx;  # 目标是常量;指针是常量;

#类内部常量
class A{
	private:
		static const int xxx=xxx;
}
 

3、尽量用const

const int* xx=xx; #指向常量
int* const xx=xx; #指针是常量
cont xxx operator*();
#避免 (a*b)=c
class A{
	public:
		const int& operator[](int i) const; //常量成员函数
		int& operator[](int i); // constness可以被overloaded
}
//STL iterators

std::vector vec;
const std::vector::iterator iter ; // T* const
std::vector::const_iterator cIter; // const T*
const_cast //去掉const,不安全
static_cast //加上const
 

4、确保对象被初始化

读取一个uninitialized values(未初始化值)会引起undefined behavior(未定义行为)
 
使用member initialization list(成员初始化列表)来初始化成员
 
定义在不同转换单元内的非局部静态对象的初始化的相对顺序是没有定义的
用local static objects取代non-local static objects , 单例模式
 
#include <iostream>

class Singleton
{
public:
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    Singleton(const Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Singleton& get_instance(){
        static Singleton instance;
        return instance;

    }
private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
};

int main(int argc, char *argv[])
{
    Singleton& instance_1 = Singleton::get_instance();
    Singleton& instance_2 = Singleton::get_instance();
    return 0;
}
 

5、了解C++为你偷偷加上和调用什么函数

  • copy constructor(拷贝构造函数)
  • constructor(构造函数)
  • copy assignment operator(拷贝赋值运算符)
  • destructor(析构函数)
 

6、如果不想用compiler-generated functions(编译器生成函数 item5)就明确拒绝

 
  • 将不想使用的‘编译器生成函数’声明为‘私有函数’ (link报错)
  • 继承第一项的基类(编译报错)
    • boost::noncopyable
  • = delete
 

7、基类的destructors函数声明为virtual

 
当一个derived class object(派生类对象)通过一个pointer to a base class with a non-virtual destructor删除,结果未定义
 
会作为基类使用的类都要将析构函数定义为虚函数
 
希望一个类为抽象类但没有纯虚函数,将析构函数定义为纯虚函数,但要提供定义
 

8、防止因为exceptions而离开destructors

 

9、不要在construction和destruction调用虚函数

 

10、assignmen operators返回一个reference to *this

以实现连续赋值的功能
x=y=z=x

void operator=(const int& x){}; #返回为void不能实现上述功能
int& operator=(const int& x){return *this); #可以实现上述功能
 

11、在operator=处理assignment to self

 
避免自我复制造成各种异常
 
自我确认
// 非异常安全
void operator=(int a){
	if this==&a {return}
 
copy and swap
void swap(int a){}

// 异常不影响原因类容(将内存申请提前)
operator=(const int &a){
	int temp(a);  // copy
	swap(a);   // swap
	return;
} // del copy

// 通过传值复制
operator=(int a){
	swap(a);
	return;
}
 

12、拷贝一个对象的所有组成部分

一个派生对象,重写拷贝函数的时候,记得调用父类的拷贝函数
 
拷贝构造和拷贝赋值不要相互调用,将相同的代码抽出来 private:init
 

13、使用对象来管理资源

c++是否应避免使用普通指针,而使用智能指针(包括shared,unique,weak)?
以下是全文内容,请读者鉴赏。 C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒,手动分配内存与手动释放内存有利也有弊,自动分配内存和自动释放内存亦如此,这是两种不同的设计哲学。有人认为,内存如此重要的东西怎么能放心交给用户去管理呢?而另外一些人则认为,内存如此重要的东西怎么能放心交给系统去管理呢?在 C/C++ 语言中,内存泄露的问题一直困扰着广大的开发者,因此各类库和工具的一直在努力尝试各种方法去检测和避免内存泄露,如 boost,智能指针技术应运而生。 在 2021 年讨论 std::auto_ptr 不免有点让人怀疑是不是有点过时了,确实如此,随着 C++11 标准的出现(最新标准是 C++20), std::auto_ptr 已经被彻底废弃了,取而代之是 std::unique_ptr。然而,我之所以还向你介绍一下 std::auto_ptr 的用法以及它的设计不足之处是想让你了解 C++ 语言中智能指针的发展过程,一项技术如果我们了解它过去的样子和发展的轨迹,我们就能更好地掌握它,不是吗? std::auto_ptr 的基本用法如下代码所示: #include int main() { //初始化方式1 std::auto_ptr sp1(new int(8)); //初始化方式2 std::auto_ptr sp2; sp2.reset(new int(8)); return 0; } 智能指针对象 sp1 和 sp2 均持有一个在堆上分配 int 对象,其值均是 8,这两块堆内存均可以在 sp1 和 sp2
c++是否应避免使用普通指针,而使用智能指针(包括shared,unique,weak)?
// std::unique_ptr
 

14、谨慎考虑资源管理类的拷贝行为

通常禁止拷贝
 

15、在资源管理类中访问裸资源(raw resources)

显示转换更安全
 

16、new delete 、new [] delete [] 匹配使用

// 尽量不要typedef数组
typedef int aa[5];

new aa;
delete [] aa;
 

17、在一个独立语句构建智能指针

 
 

18、是接口易于正确使用,难以错误使用

 
  • 尽量与内建类型一直
    • 按惯例写代码
  • 消除客户得资源管理职责
    • 智能指针
    • 限定类型此操作
    • 约束对象的值
    •  

19、视类设计为类型设计

良好的类型拥有简单自然的语法,符合直觉的语义,以及一个或更多高效的实现
  • 如何创建、销毁
    • operator new
    • operator new[]
    • operator delete
    • operator delete[]
    • 构造函数
    • 析构函数
  • 对象初始化和对象赋值的不同
    • 构造函数
    • 赋值运算符
  • 以值传递(passed by value)
    • 拷贝构造函数
  • 新类型合法值的限定条件
  • 是否放进一个继承图表(是否继承或被继承)
    • virtual
  • 允许哪种类型转换
    • 类型转换函数
  • 哪些运算符和函数需要实现,哪些标准函数不被接受
  • 成员的访问权限:private protected public friend
  • 性能考虑、异常安全、资源使用(锁,动态内存)
  • 类的通用性、类模板
  • 是否真的需要这个类
 

20、pass-by-reference-to-const replace pass-by-value

限于自建类型
对于内建类型和STL的迭代器和函数对象直接传值
 

21、返回对象不用引用

 

22、数据成员声明为private

 

23、用非成员非友元函数取代成员函数

属于方便性函数,减少对private数据成员访问的函数
 
更好的封装
 
放在同一个命名域中
 

24、当类型转换应用与所有参数时,声明为非成员函数

operator*(int a,int b);
 

25、考虑支持不抛异常的swap

 
 

26、只要有可能就推迟变量定义

 

27、将强制转型减到最少

 
  • cosnt_cast
    • 消除常量属性,唯一可以做到
  • dynamic_cast
    • 安全的向下转型(safe downcasting)
  • reinterpret_cast
    • 底层强制转型,导致实现依赖不可移植
  • static_cast
    • 强制隐形转换
 

28、避免返回对象内部构件的“句柄”

句柄:持有其它对象的方法,引用、指针、迭代器
 
一个句柄被反悔了,就面临句柄比它引用的对象更长寿的风险
 
 

29、争取异常安全(exception-safe)的代码

 
  • 资源泄露
    • 资源管理类 item14
  • 数据结构恶化
    • 函数提供基本保住(the basic guarantee)
      • 异常被抛出,没有数据结构被破坏
    • 函数提供强力保住(the strong guarantee)
      • 异常被抛出,程序状态不变
    • 函数提供不抛出保住(the nothrow guarantee)
      • 允诺不抛错
      •  

30、理解inline化的介入和排除

 
 

31、最小化文件之间的编译依赖

 
对声明的依赖替代对定义的依赖(非实现依赖)
 
最小化编译依赖的一般做法是依赖于声明而非定义,这个想法可以通过句柄类或接口类来实现。
库的声明应当包括“完整的”和“只有声明的”两种形式。
 

32、✳确保public inheritance模拟“is-a"

D继承B,每个D都是一个B,但每个B不一定是个D
 

33、避免覆盖(hiding)继承得到的名字

继承类中的同名方法会将基类的同名方法全部覆盖(包括重载)
 
可以通过using declartions或者forwarding functions
 

34、区分inheritance of interface(接口继承)和inheritance of implementation(实现继承)

 
纯虚函数只继承接口
简单虚函数继承接口和实现
非虚函数继承接口且强制继承实现
 

35、考虑可选的virtual fuctions(虚拟函数)的替代方法

  • non-virtual interface idiom(NVI idiom) 用共有非虚拟函数包装私有虚拟函数
  • 函数指针代替虚拟函数(策略模式)
    • tr1::function代替函数指针
      • 分开两个继承体系
notion image
 

36、绝对不要重定义一个inherited non-virtual function(通过继承得到的非虚函数)

#item 33
 
 

37、绝不要重定义一个函数的inherited default parameter value(通过继承得到的缺省参数值);不要在虚函数上加默认参数

 
基于item 36只讨论虚函数
 
虚函数是动态绑定,缺省参数是静态绑定
 
如要使用默认参数,item35
 

38、通过composition(复合)模拟“has-a”(有一个)或“is-implemented-in-terms-of”(是根据...实现的)

 

39、谨慎使用private inheritance(私有继承)

 
pirvate inheritance意味着“is-implemented-in-terms-of"可以用item 38替代
缺点:
不能被派生
编译依赖 #item 31
有点:
空类优化
 

40、谨慎使用multiple inheritance(多继承)

multiple inheritance(多继承)比 single inheritance(单继承)更复杂。它能导致新的歧义问题和对 virtual inheritance(虚拟继承)的需要。 virtual inheritance(虚拟继承)增加了 size(大小)和 speed(速度)成本,以及 initialization(初始化)和 assignment(赋值)的复杂度。当 virtual base classes(虚拟基类)没有数据时它是最适用的。 multiple inheritance(多继承)有合理的用途。一种方案涉及组合从一个 Interface class(接口类)的 public inheritance(公有继承)和从一个有助于实现的 class(类)的 private inheritance(私有继承)
 

41、理解implicit interfaces(隐式接口)和compile-time polymorphism(编译器多态)

 
STL 编译器多态 隐式接口
继承多态 运行时多态 显式接口
 

42、理解typename的两个含义

  • 在声明 template parameters(模板参数)时,class 和 typename 是可互换的。
 
  • 用 typename 去标识 nested dependent type names(嵌套依赖类型名),在 base class lists(基类列表)中或在一个 member initialization list(成员初始化列表)中作为一个 base class identifier(基类标识符)时除外。
    • 用于区分是类型还是变量,如果能推断出只能是类型就不需要typename
    •  

43、了解如何访问templatized base classes(模板化基类)中的名字

 
在 derived class templates(派生类模板)中,可以经由 "this->" 前缀,经由 using declarations,或经由一个 explicit base class qualification(显式基类限定)引用 base class templates(基类模板)中的名字。
 

44、从templates中分离出parameter-independent(参数无关)的代码

templates(模板)产生多个 classes 和多个 functions,所以一些不依赖于 template parameter(模板参数)的模板代码会引起膨胀。 non-type template parameters(非类型模板参数)引起的膨胀常常可以通过用 function parameters(函数参数)或 class data members(类数据成员)替换 template parameters(模板参数)而消除。 type parameters(类型参数)引起的膨胀可以通过让具有相同的二进制表示的实例化类型共享实现而减少。
 
 

45、用member function templates(成员函数模板)接受all compatible types(所有兼容类型)

使用 member function templates(成员函数模板)生成接受所有兼容类型的函数。 如果你为 generalized copy construction(泛型化拷贝构造)或 generalized assignment(泛型化赋值)声明了 member templates(成员模板),你依然需要声明 normal copy constructor(常规拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)。
 

46、需要type conversion(类型转换)时在templates内定义non-member functions

类模板通过友元实现函数参数的类型转换(混合模式乘法)
 

47、为类型信息使用traits classes(特征类)

traits classes 使关于类型的信息在编译期间可用。它们使用 templates(模板)和 template specializations(模板特化)实现。 结合 overloading(重载),traits classes 使得执行编译期类型 if...else 检验成为可能。

48、感受template metaprogramming(模板元编程)

  • template metaprogramming(模板元编程)能将工作从运行时转移到编译时,这样就能够更早察觉错误并提高运行时性能。
  • TMP 能用于在 policy choices 的组合的基础上生成自定义代码,也能用于避免为特殊类型生成不适当的代码。
 

49、了解new-handler的行为

 

50、领会何时替换new和delete才有意义

 

51、编写new和delete时要遵守惯例