1.视C++为一个语言联邦
C++
C++是一个多重泛型编程语言,同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式,具有强大的能力和弹性。
四个次语言
将C++视为语言联邦,有多个次语言
- C: 没有模板,异常,重载,类的C。
- Object-Oriented C++: C with class, 封装,继承,多态,虚函数。
- Template C++: C++泛型编程。拓展出元编程能力。
- STL: 容器,迭代器,算法
什么是元编程
可以扩展程序自身,这样的能力,为元编程。
扣定义的话,动态生成html的程序也可以叫元编程。
Lisp 以元编程为基本。而 Ruby 因为元编程易用又强大。
2.尽量以const,enum,inline替换#define
#define
#define //处于预处理阶段
#define MAX 1.6
#define CALL_MAX(a,b) f((a)>(b)?(a):(b))
问题
- #define MAX 1.6
- 预处理阶段就会把MAX全换成1.6,当运用此常量得到编译错误信息后,可能不知所云,因为错误信息也许会提到1.6而不是MAX
- 可能会导致生成多个常量1.6,码量可能会大一点。
- #define CALL_MAX(a,b) f((a)>(b)?(a):(b))
a=5;
b=0;
CALL_MAX(++a,b); //a被加两次
CALL_MAX(++a,b+10); //a被加一次
//a的递增次数要看代码执行了几次。
可以改为
inline int test(const int& a,const int& b){
return a>b?a:b;
}
- 在类中使用一个不变的值,可以定义为const,最好再定义为static,只保留一份
谨记
- 对于单纯常量,最好用const或enum替换#define.
- 对于形似函数的宏,最好用inline函数替换#define.
- #define在控制编译上还是有用的,#ifdef,#ifndef
3.尽可能使用const
const
char val="hello";
char* p = val;
const char* p = val; //p指向的数据不能变
char* const p = val; //p不能变
const char* const p = val; //都不能变
除了上面的懂了,下面的,讲真的,complex。
4.确定对象被使用前已被初始化
初始化
- 为内置对象进行手工初始化;
- 对于对象,构造函数最好用初始化列表,在构造函数中写的是赋值,不是初始化。初始化次序和它们在类中声明的顺序一样。
- 单实例模式。
5.了解C++默默编写并调用哪些函数
一个空类默认生成的几个函数
- 默认构造 default
Student s;
- 拷贝构造 copy
Student s(s2);
- =重载 copy assinment
Student s;
s=s2;
- 析构函数
6.若不想使用编译器自动生成的函数,就该明确拒绝
- 将copy构造和copy assignment声明为private而不实现
- 写一个uncopyable,将copy构造和copy assignment声明为private而不实现,然后继承此类的类就都不能copy和=了。
7.为多态基类生明virtual析构函数
virtual析构函数
- 抽象类最好声明一个virtual的析构函数。如果类中带有任何virtual,都最好声明一个virtual的析构函数。
- 非基类最好不要声明virtual析构函数
8.别让异常逃离析构函数
析构函数的异常
-
析构函数最好不要抛出任何异常,不然会导致不可预料的结果。如果可能有异常,就在异常发生时强制结束程序或者记录异常,不处理继续执行。
-
析构函数中的异常处理,程序猿最好在外面自己另外定义一个函数处理。
例如:
有一个数据库管理类,在析构函数中要调用db.close()。可能会有异常。
这是应该自己写个close函数,将关闭数据库的函数写到自己的函数中,并自己调用,而不应该交给析构函数。
9.绝不在构造和析构过程中调用virtual函数
- 因为基类在构造和析构过程中调用的虚函数不能传到派生类
- 派生类实例化时,先基类调用构造函数,但此时虚函数还是一个未知的函数。
10.令operator=返回一个reference to *this
为了实现连锁赋值
- x=y=z=5 等价于 x=(y=(z=5))
- 为了实现“连锁赋值”,必须返回一个reference to *this.
int& operator=(const int& rhs){
---
return *this;
}
11.在operator=中处理"自我赋值"
- 必须判断自我赋值,不然可能出问题,例如返回本身的引用,却在赋值前先把自己delete了(书上的例子),总之实现自我赋值就是了
if(this==&rhs){
//如果是本身就不做操作
return *this;
}
else 做其他的赋值操作
12.复制对象时勿忘其每一个成分
copy构造和copy assignment要写完全
- 即该复制的都要复制
- 派生类复制对象的时候也必须调用基类的拷贝函数
- 一般自己写copy构造是需要深拷贝的情况,即有指针的情况下,不然的话默认拷贝构造可能比自己写的好用。
- 不要让赋值函数调用拷贝构造函数,建立独立的成员函数来做这件事。一般是private函数并命名为Init。所有copying函数共同调用此函数
13.以对象管理资源
资源
- 动态分配内存
- 文件描述符
- 互斥锁
- 图形界面中的笔刷
- 数据库连接
- socket
- …
资源释放
-
获得资源后立即放进管理对象,如智能指针auto_ptr,定义为智能指针,自己不用释放,指针结束后会自己调用析构函数释放资源。
-
管理对象运用析构函数确保资源被释放
-
两个RAII(罐装容器):tr1::shared_ptr 和 auto_ptr. RAII设计还是比较精巧的,而且很有用,不过比较难用。
-
tr1::shared_ptr 的复制会增加引用计数,析构会减少引用计数。当引用计数变成0才真正释放资源。
-
auto_ptr 复制的时候,原来的指针变为NULL,只保留一份资源
14.在资源管理类中小心coping行为
- 复制RAII对象必须一起复制他所管理的资源,资源的copying行为决定了RAII(罐装容器)对象的copying行为
- RAII即对对象资源处理的封装,要控制释放资源。
- RAII class copying行为一般有两种:禁止copy,对资源引用计数。
15.在资源管理类中提供对原始资源的访问
- 每一个RAII class应该提供一个”取得资源” 的办法
- 比如tr1::shared_ptr 提供了get函数
16.成对使用new和delete时要采取相同形式
- new[] --> delete[],即new中不用[],delete也不要用[],要不就都用。
17.以独立语句将newed对象置入智能指针
- 总结就是 new 一个对象和把它放入一个只能指针中间不能再执行其他代码,不然抛异常的话,对象没有被智能指针管理,就会造成内存泄露。书上的例子比较有意思。
18.让接口容易被正确使用,不易被误用
-
不一致性造成的开发人员心理和肉体上的摩擦与争执,没任何一个IDE可以完全消除
-
“促进正确使用”的办法包括接口的一致性以及与内置类型的行为兼容
-
“阻止误用”的办法包括建立新类型、限制类型上的操作、束缚对象值、消除客户的资源管理责任
-
tr1::shared_ptr 支持定制型删除器。可被用来自动解除互斥锁
19.设计class犹如设计type
- 就是设计class时要考虑全面
20.宁以pass-by-reference-to-const替换pass-by-value
- 自己定义的对向尽量引用传递,而不是值传递。
- 引用传递可以避免切割问题。什么是切割问题呢?就是传一个带有自己特性的子类对象,若直接值传递,就会复制,可能就传的是个基类对向了。如传Windows窗口,具体看书吧。
21.必须返回对象时,别妄想返回其reference
- 不要返回一个reference指向local stack对象或者heap-allcated对象
22.将成员变量声明为private
-
这可赋予用户数据访问的一致性。
-
protected不比public更具封装性
-
封装性与 “当其内容改变时可能造成的代码破坏量” 成反比。(封装的越好,内容改变造成的破坏越小)
23.宁以non-member、non-friend替换member函数
- 即如果可以的话,尽量非成员函数实现某些功能,可以传入对向的引用对对象进行操作。
24.若所有参数皆需类型转换,请为此采用non-member函数
-
令classes支持隐式类型转换是个糟糕的主意,即最好加上explicit声明禁止隐式转换。
-
如果需要为某个函数的所有参数进行类型转换,那么这个函数必须是一个non-member
25.考虑写出一个不抛异常的swap函数
-
标准库的swap(T &a,T &b);仅支持T类型有copy构造或copy assignment的情况。
-
需要自己写swap的时候要注意,,,一般也不需要自己写吧。
26.尽可能延后变量定义式的出现时间
-
对象的构造和析构都会造成开销。尽量在使用的时候再定义。
-
最好在定义的时候初始化,可以省去默认构造的开销。
27.尽量少做转型动作
转型
(1)旧式转型
(int)a;
int(a);//都是将a转换成int类型
(2)C++新式转型
const_case<T>( expression ) //移除对象的常量性
dynamic_case<T>( expression ) //安全向下转型
reinterpret_case<T>( expression ) //低级转型
static_case<T>( expression ) //强迫隐士转换,和(int)a类似
注意
- 尽量避免转型
- 如果必须转型,应将转型动作隐藏在函数里,客户直接调用函数。
- 尽量使用C++新式转型
28.避免返回handles指向对象内部成分
-
不要返回内部handle,也不要返回指向访问级别较低的成员函数。降低dangling handle的可能性
-
即public函数不要返回private成员的引用,必须返回的话,要加上const,即返回const XXX& 类型。
29.为“异常安全”而努力是值得的
异常安全
- 不泄露任何资源
- 不允许数据败坏
异常安全函数的三个保证
- 基本承诺:如果异常抛出,程序的其他数据仍然保持有效状态,不会被破坏,例如改变屏幕背景,若失败,背景应该为改变前的样子或默认背景。
- 强烈保证:如果函数失败,会回到程序调用函数前的状态。能通过copy-and-swap实现,但并非对所有函数都可实现且有意义。
- 不抛掷保证:承诺不抛出异常,函数总能完成它该有的功能。
30.透彻了解inlining的里里外外
- inline应该用在小型的、频繁的函数,主要是省去函数调用的开销。
- 若过多用inline,代码量就会增大,代码膨胀可能会导致额外的换页行为,降低指令告诉缓存的命中率,随而降低效率。
31.将文件间的编译依存关系降至最低
- 能使用reference 和pointer的,就不要用object,就是尽量用引用和指针。
- 类中用对象的话,编译阶段就会确定类的大小,用指针的话计算的是指针的大小。
- 类中尽量用指针,不过用指针的话要注意深度拷贝和析构的问题,默认析构函数不会delete指针成员
- 程序库头文件应该仅有声明式
32.确定你的public继承塑模出is-a关系
- 如果要继承的话,一定要是is-a关系,即基类的每个函数都适用于派生类
33.避免遮掩继承而来的名称
-
在子类中的同名成员会遮掩基类中的成员
-
适用using:Base::函数名(),就可以让基类中使用基类中的函数
- 若基类有多个重载,子类中有重名函数就会覆盖所有重载,而使用using就可以重新用那些重载
- 若基类中是私有变量,使用using就能在子类中使用
34.区分接口继承和实现继承
-
pure virtual —— 接口继承,在子类中必须实现
-
non-pure virtual —— 接口 + 缺省实现继承,子类中可以再实现,也可以用默认的,不过有时候不想用默认的却会忘了实现,可以看书中飞机例子的解决办法。
-
non virtual —— 接口+强制实现继承,子类中不能更改它的行为
35.考虑virtual函数以外的其他选择
- 不一定非得用virtual函数,替代方案有很多,比如:函数指针成员变量,NVI手法,Strategy设计模式等
36.绝不重新定义继承而来的non-virtual函数
- 对应34条,不要在子类中重新定义non-virtual,若需要重写,就把基类中的定义为virtual(怎么感觉又和35矛盾了。。。)
37.绝不重新定义继承而来的缺省参数值
- 主要是对virtual的子类实现来说,若基类中virtual函数有缺省值,子类中不应该改变。
- 缺省值是静态绑定的,而virtual是动态绑定的。
38.通过复合塑模出has-a或“根据某物实现出”
- “复合(composition)”的近义词:组合,分层(layering),内含(containment),聚合(aggregation),内嵌(embedding)
- 要有 has-a 的关系
39.明智而审慎地使用private继承
-
一般而言,不需要用private继承。复合基本可以完全包含private继承的能力
-
一般必须要用virtual函数的时候可以用私有继承,否则复合基本可以完全包含private继承的能力
-
2可能说的不对0.0
40.明智而审慎地使用多重继承
-
多重继承可能导致成员歧义,即基类中有相同的成员名字
-
菱形/钻石继承,所有继承的类都变为虚继承,但是,虚继承的那些class产生的对象往往比使用non-virtual继承的体积大,访问这些成员变量也相对较慢。
41.了解隐式接口和编译期多态
-
在构造函数前声明 explicit 关键字,禁止隐式转换,从而变成显式接口
-
类中某些成员函数是virtual的,当调用这些函数的时候就表现出运行期多态。
-
编译期多态主要是对templates模板来说,类型的多态
-
讲真这条不太理解在强调什么。
42.了解typename的双重意义
- 声明template时,可以用class或typename,二者等价
template<class T>
template<typename T>
43.学习处理模块化基类内的名称
- 不太懂,等需要模板编程的时候再细看吧。
44.将与参数无关的代码抽离templates
-
若两个函数实现过程中有很多重复代码,应该将重复代码单独写成一个函数,调用之。
-
对模板同理,至于怎么操作。。。
45.运用成员函数模板接受所有兼容类型
- 模板这块先略过吧,没有什么编程需求,所以没有直观感受。
46.需要类型转换时请为模板定义非成员函数
- 模板这块先略过吧,没有什么编程需求,所以没有直观感受。
47.请使用traits classes表现类型信息
- 模板这块先略过吧,没有什么编程需求,所以没有直观感受。
48.认识template元编程
-
元编程执行于编译期,可以把工作转移一部分到编译期,而且一些在运行期才能检查出来的错误也能转到在编译期检查出来。
-
通过template<当做参数>实现
49.了解new-handler的行为
- new_handler声明与std
namespace std{
typedef void (*new_handler);
new_handler set_new_handler(new_handler p) throw();
}
-
new_handler 是一个函数指针,它有什么用呢?
当new一块空间不足时,它就会抛出异常,但在异常之前会先调用一个客户指定 的错误处理函数,就是new_handler,通过ser_new_handler(函数名)来设置。 -
卸载new_handler
set_new_handler(NULL) -
作用局限,只适用于内存分配,后续的构造函数调用可能还是会抛出异常。
50.了解new和delete的合理替换时机
-
一般替换掉默认的new和delete一般是为了: 提高性能(比如tcmalloc),收集数据、检测错误等。比如valgrind之类(Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。)
-
替换方法:简单来说就是封装malloc和free,relloc等,做出自己想要的效果。。。。简单来说。
51.编写new和delete时需固守常规
-
无限循环尝试分配内存,若无法满足内存需求,就该调用new_handler
-
也要有能力处理0bytes申请。还应该能处理比正确大小更大的申请
-
delete 应该在收到NULL指针时不做任何事
52.写了placement new也要写placement delete
-
主要讨论一个在构造函数中抛出异常会不会内存泄露的问题。placement new/delete 就是在类中自己重载new和delete操作符。
-
一个对象在构造函数中抛出异常,对象本身的内存会被成功释放,但是其析构函数不会被调用。其内部局部成员对象清栈时是会被释放掉的,故其析构函数被调用,但是用户在构造函数中动态生成的对象没有被delete掉(new出来的是一个指针,清栈时是不会delete掉栈里的指针)。
-
如果一个对象在构造函数中打开很多系统资源,但是构造函数中后续代码抛出了异常,则这些资源将不会被释放,建议在构造函数中加入try catch语句,对先前申请的资源进行释放后(也就是做析构函数该做的事情)再次抛出异常,确保内存和其他资源被成功回收。
53.不要轻忽编译器的警告
- 额总之就是在不过度依赖编译器的情况下达到0警告
54.让自己熟悉包括TR1在内的标准程序库
C++98加入的新特性
- STL
- iostreams
- 国际化支持,如:wchar_t等
- 数值处理:如复数模板complex
- 异常阶层体系
- C89标准程序库
TR1
14个新组件,std::tr1::shared_ptr
- 智能指针
- tr1::function,传递函数指针,详情再看教程吧。
- tr1::bind,比bind1st,bind2nd更好用的STL绑定器,绑定的函数有点像传递仿函数的功能。
- Hash tables,STL的map和se是用红黑树实现的,这实现的Hash结构的。
- 正则表达式
- Tuples(变量组)
- tr1::array
- tr1::mem_fun
- tr1::reference_wrapper
- 随机数,超越rand,rand是C库的
- 数学特殊函数,拉格朗日等
- C99 兼容扩充
tr2
- type traits
- tr1::result_of
???
看到最后,来一句:TR1只是规范,还没有实物???但是智能指针什么的《C++ primer》都说了。。。好吧,05年的书太早了,看了上面这么多,瞬间觉得自己根本不懂C++,都快C++20了,complex,学的好累0.0,还是python简单些。。。
55.让自己熟悉Boost
定义
- Boost是一个C++开发者的社群,也是一个免费的C++程序库群。它的网址是www.boost.org
目标
- 作为一个“可被加入标准C++库的测试场”,以TR1提案进入标准C++的14个新程序序库中,超过三分之二是基于Boost的成果
网上言论
- 说尽量用STL不要用boost什么的,现在很多boost都成标准了什么的。。。崩溃,STL还了解一些,不过理解也就是一些容器迭代器仿函数什么的。看了boost那么大,及其崩溃,不知从何学起。唉慢慢来吧。