C++凛散知识(持续更新)

290 阅读8分钟

1)视C++为一个语言联邦:

1-以C为基础:区块、语句、预处理器、内置数据类型、数组、指针等等。

2-C++ C With Classes:classes(包括构造函数和析构函数),封装、继承、多态。

3-C++ 泛型编程:Template设计编程

4-STL:STL是一个template库,对容器、迭代器、算法以及函数对象的规约协调。

2)尽量用const/inline/enum替换#define

CC:对于单纯常量,最好以const对象或enums替换#defines。

对于形似函数的宏,最好改用inline函数替换#defines。

3)尽可能使用const

CC:将某些东西声明为const可帮助编译器侦测出错误用法。

const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体

4)确定对象使用前已先被初始化

CC:构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。

5)了解C++默默编写并调用哪写函数

1-函数默认:构造、析构、拷贝

2-递归函数: 递归条件(指的是函数调用自己), 基线条件(函数不再调用自己)

3-D&C方法:找出基线条件(很可能是空数组或只包含一个元素的数组)、不断将问题分解(或缩小规模),直到符合基线条件

4-栈两种操作:压入(插入)、弹出(删除并读书)

5-函数:提供定义函数,提供函数原型,调用函数

6)为多态基类声明virtual析构函数

CC:带多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该有一个virtual析构函数。

7)别让异常逃离析构函数

CC:析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下它们(不传播)或结束程序。

如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该操作。

8)绝不在构造和析构函数过程中调用virtual函数

CC:在构造和析构函数期间不要调用virtual函数,因为这类调用从不下降至derived class(派生类)。

9)令operator=返回一个reference to(引用)指针

CC:令operator=(const typeName & rhs)返回一个reference to *this。

10)在operator=中处理"自我赋值"

CC:确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

11)复制对象时勿忘其每一个成分

CC:复制函数应该确保复制“对象内的所有成员变量”及“所有基类成分”。

不要尝试以某个复制函数实现另一个复制函数。应该将共同机能放进第三个函数中,并由两个复制函数共同调用。

12)以对象管理资源/“资源取得时机便是初始化时机”(RAll)

CC:所谓资源就是,一旦用了它,将来必须还给系统。

~获得资源后立刻放进管理对象内。

~管理对象运用析构函数确保资源被释放。

为防止资源泄漏,请使用RAll对象,他们在构造函数中获得资源并在析构函数中释放资源。

两个常被使用的RAll 类分别是trl::shared_ptr和auto_ptr。前者通常是较佳选择,因为其复制行为比较直观。若选择auto_ptr,复制动作会使它指向null.

13)在资源管理类中小心复制行为

CC:当一个RAll对象被复制时,则要么禁止复制(并不合理),要么对底层资源祭出“引用计数法”(有时希望保有资源,直到它的最后一个使用者(某对象)被销毁。

复制RAll对象必须一并复制它所管理的资源,所以资源的复制行为规定RAll对象的复制行为。

普遍而常见的RAll 类的复制行为是:抑制复制、施行引用计数法。但其他行为也可能都被实现。

14)在资源管理类中提供对原始资源的访问

APIs(应用程序)往往要求访问原始资源,所以每一个RAll类应该提供一个“取得其所管理之资源”的办法。

对原始资源的访问可能经由显示转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。

15)成对使用new和delete时要采取相同形式

CC:如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。

16)以独立语句将newed对象置入智能指针

CC:以独立语句将newed对象储存(置入)智能指针内。如果不这样做,一旦异常被抛出,又可能导致难以察觉的资源泄漏。

17) 让接口容易被正确使用,不易被误用

 struct Day{
     explicit Day(int d)
     :val(d){}
     int val;
 };
 
   struct Month{
     explicit Month(int m)
     :val(m){}
     int val;
 };
 
   struct Year{
     explicit Year(int y)
     :val(y){}
     int val;
 };
 
 class Date{
     public:
     Date(const Month & m,const Day & d,const Year & y);
     ...
 };
 Date d(30,3,1995);                    //错误!不正确的类型
 Date d(Day(30),Month(3),Year(1995));  //错误!不正确的类型
 Date d(Month(3),Day(30),Year(1995));  //ok,类型正确

CC:“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。

“阻止误用”的办法包括建立新类型、限制类型的上的操作,束缚对象值,以及消除客户的资源管理责任。

trl::shared_ptr支持定制型删除器。可防范DLL问题,可被用来自动解除互斥锁等。

18)设计class犹如设计type

CC:新type的对象应该如何被创建和销毁?

对象的初始化和对象的赋值该有什么样的差别?

新type的对象如果被passed by value,意味着什么?

什么是新type的“合法值”?

你的新type需要配合某个继承图系吗?

你的新type需要什么样的转换?

什么样的操作符和函数对此新type而言是合理的?

什么样的标准函数应该驳回?

谁该取用新type的成员?

什么是新type的“为声明接口”?

你的新type有多么一般化?

你真的需要一个新type吗?

Class的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过以上所述。

19)宁以pass-by-reference-to-const替换pass-by-value

CC:尽量以宁以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题。

以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当。

20)必须返回对象时,别妄想返回其reference

CC:绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。

21)将成员变量声明为private

CC:切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供Class作者以充分的实现弹性。

proteced并不比public更具封装性。

22)宁以non-member、non-friend替换member函数

CC:宁以non-member、non-friend替换member函数。这样做可以增加封装性、包裹弹性和机能扩充性。

23)若所有参数皆需类型转换,请为此采用non-member函数

CC:如果你需要为某个函数的所有参数(包括this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。

24)考虑写出一个不抛异常的swap函数

CC:当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。

如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes(而非templates),也请特化std::swap.

调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。

为用户定义类型进行std:: templates全特化式是好的。但千万不要尝试在std内加入某些对std而言全新的东西。

25)尽可能延后变量定义式的出现时间

CC:尽可能延后变量定义式的出现时间。这样做可增加程序的清晰度并改善效率。