Effective C++
最近借到了一本朋友的Effective C++ 这本书来读,其实好久之前就听说过这本书了,只是一直没有机会去看,对我这种菜
鸟来说我目前为止读过的书什么谭浩强的C语言那些课本不算的话,我也只读过C和指针,C陷阱与缺陷,C++的书籍中C++
prime读过一点,高质量C++编程,还有一本鸟哥的linux私房菜扔在书柜上吃了好久的灰也没看 哈哈. 我以前看的这些书
有的是讲概念,有的是讲约束程序员的守则,其实C++高质量编程和C陷阱与缺陷当中更多的是对你的编程习惯的约束,还
有就是对一些有坑的知识给你讲清楚应该怎么用不该怎么用让你少吃一点bug,这两天我也看了50多页的Effective C++吧
我感觉这本书更多的好像是教你如何高效的写出C++程序,它的每一个条款下面都有可以优化程序提高程序的效率的知识
再或者提高程序的代码复用,也就是更多的在教你一个程序的如何不出错的情况下更加高效. 所以我准备认认真真的读这
本书,可能会刷好几遍,因为很喜欢这本书无论是从排版还是书样都是很喜欢的. 下面就是我对一些知识的总结.
条款1:视C++为一个语言联邦
如何理解C++? 最简单的方法就是将C++视为一个由相关语言组成的联邦而非单一语言. 在其个次语言中,各种守
则与通
例
都倾向于简单,直观易懂,并且容易记住.然而当你从一个次语言移往另一个次语言时,守则可能发生改
变. 为了理
解C++
,主要的次语言为4种.
C : C语言的概念不用我解释了吧~
Object-Oriented C++: 这部分也就是来自C with class 所诉求的: classes,封装 , 继承 ,多态 ,virtual
函数等
等,
这一部分是面向对象设计之古典守则在C++上的最直接实施.
Template C++:这是C++的泛型编程部分,Template相关考虑与设计已经弥漫整个C++,良好编程守则中"唯template
适用"
的特殊条款并不罕见,
STL:STL是一个template程序库,看名称也知道,但它是一个非常特殊的一个. 他对容器,迭代器,算法以及函数
对象
的
规约有极佳的紧密配合与协调,然而template及程序库也可以其他想法减置出来,STL有自己特殊的办事方式
当你伙
同STL
一起工作,你必须遵守它的规约.
记住这四个次语言,当你从某个语次语言切换到另一个,导致高质量编程守则要求你改变策略时,不要感到惊讶。例如
对
内置类型而言pass-by-value通常比pass-by-reference高效,但是你从C part to C++移往Object-Oriented C++,由
于用
户自定义构造函数和析构函数的存在,pass-by-reference-to-const往往更好,运用Template C++时尤其如此.
总地来说,C++高效编程守则是状况而变化,取决于你使用C++的那一部分.
条款2:尽量使用const,enum,inline替换#define
这个问题其实都已经老生常谈了,我们从学习C语言开始到现在的C++一直都能听到有人说宏的坏话,不过为什么宏还
是会
有人用,也就是说它还是会有自己的应用场景,所以耐心了解它,以及学会用const enum inline替代#define.
首先使用常量替换宏
当我们以行列替换#define时,有两种情况值得说一说. 第一是定位常量指针. 由于常量定义式通常被放到头文件内,
因此
有必要将指针声明为const,例如若要在头文件上定义一个常量的 char*-based字符串,你必须写CONST两次.
const char* const authorName = "hheheheehe";
但是string总是比char*-based好用的多。也可以这样写.
const std::string authorName("hehege");
第二个值得注意的是class专属变量.为了将常量的作用域限制于class内,你必须让它成为class的一员;而为确保此常
量之
内至多只有一份实体,你必须让它成为一个static成员:
class Gaeplayer{
private:
static const int NumTurn = 5 //常量声明式
int scores[NumTurns];
...
}
下面是书中的一个关于宏的例子:
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a):(b));
int a = 5,b = 0;
CALL_WITH_MAX(++a,b) //a被累加两次.
CALL_WITH_MAX(++a,b+10) //a被累加一次.
即使有人告诉过我,当我写出宏的时候都要加小括号.但是这个宏的a的递增次数取决于"它被拿来和谁作比较;
但是这种代码我们还是别写出来好了,我们来看一个template版本的.
template<typename t>
inline void callWithMax(const T& a,const T& b)
{
f(a > b ? a : b );
}
这个template产出一整群函数,每个函数都接受两个同型参数,并以其中较大的调用f. 美滋滋
有个const,enums,inlines,我们对预处理器的需求降低了,但并非完全去除,#include仍然是必需品.
总结
对于单纯的常量,最好以const对象或enums替换#defines
对于形似函数的宏,最好改用inline函数替换#defines.
条款3:尽可能使用const
const的意见奇妙事情是,它允许你指定一个语义约束,而编译器会强制实施这项约束.他允许你告诉编译器和其他程序
员某值不能
改变,这样这个值不能改那么你就要说出来,因为说出来有助于编译器的帮助.
const变量的基本操作我有一篇博客介绍:const的初级使用 今天我给大家说一个比较骚的优化方案.
举个例子我们以前写过的一个类,我们会使用operator[]来返回一个reference的指向,这个一般情况我们都会写一个
const的也会写
一个非const的opeartor[].这是我们最常见的一个代码:
T& operator[](int position)
{
return xxx[position];
}
T& operator[](int position) const
{
return xxx[position];
}
边界检查,日志访问信息,还有什么数据完善性检验等等一大堆繁琐的代码,这个时候当你实现operator[] const和
operator[]()
const,的时候两份代码大部分都一样,这里伴随的是代码重复,编译时间变长,维护代码膨胀等等头疼
的问题. 当然啦,你可以让
上述那些繁琐的函数全部封装的别的函数中,然后分别在operator[]()和operator[]() co
nst当中调用但是你还说重复了一些代码
比如两次return语句,函数调用.
真正该做的是实现operator[]的机能一次并使用它两次。也就是你只需要写一个函数,令另外一个调用这个,这促使我
们将常量性
转移. 接下来 见证奇迹我们来看看下面这个代码是怎么实现的上述的操作的:
class TextBlock
{
public:
...
const char& operator[](std::size_t position) const
{
...
...
...
return text[position];
}
const char& operator[](std::size_t position)
{
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
};
来仔细看这个操作;return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
首先把*this强制转换为const TextBlock,再然后调用const的operator[],最后再把const的operator[]的返回值的
const常量性取消,然后返回一个非const的值. 这里的调用实在是太妙了,我们可以思考一下,好好想想这里的深意.
但是会有人说,为什么不用const operator[]调用operator[]呢,这样强制两个都可以行的通啊.这样想是错的!
令const版本调用调用no-const版本以避免重复并不是你该做的事情. 记住const所修饰函数的承诺就是我绝对不会修
改你
,no-const函数可没有这种承诺,所以你让一个const函数去调用一个no-const函数是不现实的. 懂了吗朋友~
条款4: 确定对象被使用前已经被初始化
反正我就是这么给你讲无论你何时何刻创建了一个东西,你一定先对他初始化,这个是属于无解的一个规则,你只要
记住这个规则
这个条款就算是你掌握. 但是有一个更难受的场景. 比如下面这个例子:
class FileSytem{ //来自你的程序库
public:
...
std::size_t numDisks() const; //众多成员函数之一
...
};
extern FileSyte tfs; //预备给客户使用的对象,tfs代表 "the file system"
class Directory
{
public:
Directory(params);
...
};
Directory(params)
{
...
std::size_t disks = tfs.numDisks(); //使用tfs对象
...
}
Directory tempDir(params);
的tfs.但是如果
这两个类定义在不同的文件不同的时间中,他们是定义在不同编译单元的non-local static对象.如何确定tfs会在
tem
之前被初
始化?
这个就非常困难,甚至连一个特例的解决方案都找不到,消除这个隐患的方法只有一个! 那就是将non-local static
对象搬到自己
的专属函数当中,然后让函数返回一个referencr指向它的对象,然后用户调用这些函数,而不直接涉及
对象.
这方法有啥用? 这里就有一个C++的标准,函数内的局部静态变量对象,会在该函数调用之前"首次碰到该对象之定义式
"时被初始
化.所以你就能保证你拿到的一定是一个初始化过的tfs,就不用担心不定向行为了. 可以这样修改程序:
class FileSytem{ /*来自你的程序库*/ };
FileSytem& tfs() //这就是用来替换tfs对象的函数.
{
static FileSytem fs;
return fs;
}
class Directory{ .... };
Directory::Directory(params)
{
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir()
{
static Directory td;
return td;
}
tfs和tempDir。
也就是说他们使用函数返回的"指向static对象"的reference,而不再是static对象本身.
总结
1.对内置类型对象进行手工初始化,因为C++不保证初始化他们.
2.构造函数最好使用初始化列表.
3.为免除"跨编译单元之初始化次序"问题,使用局部静态对象来替代非局部静态对象解决.
条款5:了解C++默默编写了并调用了那些函数
这里看我的这篇博客:类中6个默认成员函数