1.c++的四大类(c、c++、template、stl)

2.#define的非标准用法

理由:1.用#define表示常量,一旦编译错误,错误信息的提示很难读懂。2.用#define表示函数来降低函数调用的开销,会造成执行期的问题。

经验:1.用const或enums来代替#define。2.用inline代替#define。

3.尽可能使用const

理由:const使用可以获得编译器的帮助。

经验:1.能用const就用,应用于函数参数、返回值。2.const成员函数用来操作const对象

4.确保内置类型使用前被初始化、赋值与初始化

理由:1.内置类型并不保证被正确自动初始化,要手动。2.赋值相比初始化要耗费更多的资源。3.非函数内的静态变量在其他编译单元内被使用时,不能保证被初始化。

经验:1.永远在使用对象之前对其进行初始化。2.确保任何构造函数内初始化了每个成员。用初始化成员列表来初始化,初始化成员列表的初始化顺序与成员声明的顺序有关。3.用静态方法返回静态对象。因为local static对象会在“该函数被调用期间”首次遇上该对象定义式时被初始化。

5.c++对于类默默编写哪些函数

defualt构造函数、copy构造函数、copy assignment操作符。

6.若不想使用编译器自动生成的函数,就应该明确拒绝

理由:由于编译器会默认生成4大函数,如果不想使用,就要进行一些处理。

经验:1.可以手动声明4大函数,并将其声明为private,同时在定义中不实现。2.可以private继承一个手动实现了private的类,这样在使用friend的时候会有一些优势。

7.为多态基类声明virtual析构函数

理由:如果class备用做基类来实现多态,基类指针在析构的时候如果析构函数不是virtual,则会造成内存泄露。

经验:如果一个类作为基类来实现多态,则一定要声明虚析构函数。否则析构函数不声明为virtual。

8.别让异常在析构函数中抛出,不让异常传播

理由:抛出异常的栈展开过程,编译器要确保抛出异常前创建的对象通过其析构函数被销毁。如果析构函数抛出异常,则程序直接terminate,不能进入栈展开的处理流程。

经验:1.析构函数不应该抛出异常,如果可能,则捕捉他们然后吞下或结束程序,不让异常传播。2.如果某些在析构函数中的功能可能抛出异常,最好将其接口暴露给用户,给用户提供处理异常的条件。防止析构函数中抛出异常。

 9.构造函数和析构函数绝不调用virtual函数

理由:base class对象会在derived class对象创建之前构造,也就是说derived class并不存在,这是base class的virtual函数只认base class中的virtual函数。

经验:不能再构造和析构函数中使用virtual函数,如果需要derived class调用不同的base class的构造函数,可以将调用的base class函数做成一个需要参数的接口,derived class在初始化的时候给其传入一个参数即可。

10.重载运算类操作符(+、-、=+。。。)返回reference to *this

理由:一旦有连续赋值的情况出现(比如A=B=C),将会报错。

经验:赋值操作符返回一个reference to *this。

11.考虑自我赋值的情况

理由:赋值函数如果没有考虑自我赋值的情况,可能在自我赋值的时候出现将自我delete。

经验:赋值函数可以通过对比地址、调整语句顺序、copy-and-swap来实现。

12.重写拷贝构造函数、重写赋值函数要注意

经验:1.拷贝构造函数时构造函数,赋值函数不是构造函数,所以二者之间不能互相包含。2.确保复制和赋值时,操作了所有的对象。

13.以对象管理资源

理由:资源就是一旦用完,必须还给系统。比如:动态分配的内存、各种锁、数据库连接、sockets等。过早的return、抛出异常等情况会导致释放资源的语句没有执行。所以单纯的依赖deleta语句被执行来管理资源是不可靠的。

经验:1.把资源放进管理对象内,依靠该对象析构函数,来自动释放哪些资源。

    2.获取资源就要放进资源管理对象。

    3.shared_ptr引用计数器智能指针。持续追踪有多少对象指向某个资源,在无人指向的时候删除。但是调用的是delete而不是delete[]。如果有动态数组的需求,就用vector。

14.小心资源管理类的copy行为(比如mutex的复制)和资源管理类的删除器

理由:比如mutex的复制就是不合理。并且释放资源的动作是解除锁定而非删除。所谓的“删除器”,就是当shared_ptr引用次数为0的时候就被调用的函数。

经验:1.释放资源的动作不是删除,就要自己制定删除器。2抑制copy行为。

15.在资源管理类中提供对原始资源的访问

理由:智能指针一个指针管理类,是一个类型,但是应该像一个指针,或者提供返回指针的功能。可以显示(get)或者隐式(重载->)来实现。

经验:显示转换相对于隐式转换来说比较安全

16.new搭配delete,new[]搭配delete[]

理由:delete值删除一个,只有告诉编译器deleta[],编译器才能删除。

经验:记住new[]搭配delete[]使用。

17.用独立的语句将new的指针放到智能指针

理由:c++编译器会擅自改变程序执行的顺序,造成内存泄露或者野指针。

经验:资源管理的时机是资源申请的时候,管理的方法是用独立的语句防止编译器打乱顺序。

18.设计最小犯错可能的函数接口

理由:降低客户发生错误的可能性。

经验:1.预防参数类型错误。通过导入explicit的新类型限制参数类型。2.以函数来替换对象。3.接口保持一致性。4.防止资源泄露。如果需要客户去delete,就返回智能指针。

20.函数传递参数时,用pass-by-reference-to-const替换pass-by-value

理由:1.pass-by-value会调用类的copy构造函数,这是不小的开销。2.有可能造成切割的问题。当函数的参数类型时基类的时候,如果传入的是子类对象以pass-by-value的方式传入,则会切割子类对象,留下一个基类对象。

经验:函数参数传递用pass-by-reference-to-const代替pass-by-value。

21.函数返回值要返回value别返回reference

理由:1.当返回局部变量的引用时,超出作用域,局部变量自动销毁,保存返回的reference的指针就成了野指针。

  2.当返回变量是动态分配内存而来,则有可能造成客户使用时候忘记delete,内存泄露。

  3.当返回static变量时,则可能造成同一类型的两个对象做==判断时候,值一样。

经验:函数返回值,不为reference。

22.所有成员变量都声明为private

理由:1.封装性。2.重用性(指定抽象而不是指定具体)

经验:所有成员变量都应该是private,protect并不比public更具有封装性。

23.用non-member和non-friend替换member函数

24.如果函数的所有参数(包含this)都需要类型转换,就采用non-member函数

理由:比如操作符重载(*),就不能用member函数。因为重载不了this对象。

经验:1.如果某个函数的所有参数(包括this)都要进行隐式参数转换,那么这个函数必须是non-member。2.能不用friend就不用friend,保证封装性。

25.写一个不抛出异常的swap函数

26.尽可能延后变量定义

理由:变量的定义就表示要承受变量的构造成本和析构成本,即使这个变量从未被使用。

经验:知道必须使用这个变量的前一刻,再定义这个变量。而且定义直接赋初值,尽可能不去调用default构造函数。

27.尽可能少的使用转型动作

理由:转型有很高的成本,而且会导致潜在是问题。

经验:用组合或者virtual继承来实现转型的目的。

28.避免返回指向对象内部的指针、引用或迭代器

理由:破坏了类的封装性,返回的handle可以直接修改类成员,即使成员被声明为private。

经验:避免返回handle(引用、指针、迭代器)。

29.写异常安全代码

理由:1.不泄露任何资源 2.不允许数据破坏

经验:1.资源管理(智能指针)。可以防止调用delete而导致的资源泄露。

    2.调整语句次序。

    3.不抛异常。但是使用new就无法保证不抛异常。

    4.copy and swap。将要修改的部分做成struct,并创建一个副本,未抛异常就swap副本和本体。但是这种方式会占用一些资源。

    5.异常安全具有木桶效应。

 

 

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

要解决的问题:假设A、B、C三个头文件,其中A包含B,B包含C,当C头文件更改之后,就会造成所有引用A头文件的全部重新编译。   

原理:利用编译器执行编译的条件,来避免编译器进行编译。避免头文件(外部接口)更改。

编译器编译条件:编译器编译的条件是发现一个编译的类型或者大小跟之前不一样了。

方法:使用前置声明来代替引用头文件。

经验:若想降低程序的编译时间,就用前置声明来代替include头文件。

32.public继承非is-a关系不用

33.避免继承造成的变量名称被遮掩

经验:子类名称会覆盖基类名称,通过using可以指定特定名称变量。

34.接口继承(pure virtual)接口和实现继承(impure virtual)实现继承(non-virtual)

pure virtual:只继承接口,重写实现

impure virtual:继承接口,可以有默认实现

non-virtual:强制实现

35.考虑virtual以外的其他选择(template、strategy。使用不同的设计模式来实现面向对象)
理由:为了程序的松耦合和代码的可复用性。
template与strategy区别:template相对来说依赖性更强,更适用于对象与对象之间的算法有共同结构的情况。而strategy则更适用算法与对象独立,也就是对象算法之间没有共同结构的情况。

36.绝不重新定义继承而来的non-virtual函数

37.绝不重新定义继承而来的默认参数值

理由:基类指针指向的子类对象,调用子类的函数,并不会达到虚函数的效果。

经验:绝不重写继承而来的变量或者函数,因为继承而来的是静态绑定。唯一能够复写的东西是virtual函数(动态绑定)。主要看类的设计。

38.通过复合达到has-a或is-inplemented-in-terms-of(根据某物实现出)

理由:当某种类型需要内含其他类型的时候,但是不是is-a的关系,就要用复合。

经验:复合和public继承完全不同。根据类之间的不同关系来选择类与类之间的实现关系。

39.谨慎使用private继承

理由:1.private继承的子类对象的指针并不能显式转换成父类指针,也就是说编译器判定这两个类没有关系。2.private继承而来的基类成员在子类中全部都是private属性。3.private继承在设计层面没有意义,只在实现层面有意义。子类与父类的关系是is-Implemented-in-terms-of(根据某物实现出)

经验:.尽可能选择复合来实现is-implement-in-terms-of,但是当父类中有virtual函数的时候,应该用private继承。

40.谨慎使用多重继承

理由:1.多重继承会导致歧义。2.多重继承会导致两个父类继承同一个基类,这样子类就包含两个基类。可以用vitrual继承来解决,但是virtual继承要付出代价。

经验:尽可能不用多重继承来实现类的设计。除非一种既需要继承一个类的接口,又需要根据另一个有virtual函数的类来实现的条件。也就是说多重继承也仅仅是只有一个public继承,其他的都应该是private继承。

 

 

49.了解new-handle

理由:operator new可能会产生异常,异常的捕获就是new-handle,也可以返回一个null。

经验:1.set-new-handle可以指定operator new抛出异常的捕获函数。2.nothow new并不太安全,因为他能保证当前这个operator new不抛出异常,但是建立对象的时候,对象中可能含有需要new的成员对象,一旦抛出异常就不是nothow的了。

50.了解new和delete的合理替换时机

理由:c++提供的new和delete采用的是中庸之道,如果有对速度、内存、其他特殊功能的要求,可以重写operator new和operator delete。

经验:一般条件下不需要重写operator new 和operator delete。

posted on 2018-03-03 15:29  Grant-Zhang  阅读(133)  评论(0)    收藏  举报