原创 lightcity 光城 2019-09-11收录于话题#C++那些事53个
导语
注:本节代码见github仓库
https://github.com/Light-City/effective_cpuluplus
条款2:尽量使用const, enum, inline, 减少宏变量#define的使用
尽量多用编译器,少用预处理器
#define A 3.14
替换为:
const double A = 3.14;
注意宏是全局的与范围无关!
当我们以const替换#define,两种特殊情况值得说明:
(1) 定义常量指针
例如若要在头文件内定义一个常量的(不变的)char *字符串,就必须写const两次
const char* const myWord = "xxx";
而对于上述在C++中直接使用一次const即可:
const std::string myWord("xxx");
(2)class专属常量
例如:为了将常量的作用域(scope) 限制于 class内,你必须让它成为class 的一个成员 (member) ;而为确保此常量至多只有一份实体,你必须让它成为一个static 成员:
class GamePlayer {
private:
static const int NumTurns=5;
int scores[NumTurns];
...
};
注意,因为此处是类的成员声明范围内,所以上面只是变量的声明和初始化,而并非定义,因此如果想获取变量的地址,需要在别处另加定义。这个定义不能有任何赋值语句,因为在类内已经规定为const:
const int GamePlayer::NumTurns;
使用枚举
当你在一个类内声明某变量,但你的编译器不允许在声明时赋值初始化:
int a;
int s[a];
此时s[a]肯定报错了,为了解决这种问题,可以使用枚举:
enum {a=5};
int s[a];
inline函数替代宏函数
inline关键字用来建议编译器把某频繁调用的函数当做内联函数,即在每次函数调用时,直接把函数代码放在函数调用语句的地址,减少堆栈浪费。
现在有如下例子:
#define CALL_MAX(a,b) f((a) > (b) ? (a) : (b))
当main函数中调用如下:
cout<<MAX(++a, b)<<endl; // a被增加两次
cout<<MAX(++a, b+10)<<endl; // a被累加一次
这与我们的预期结果不同!
为了解决这个问题,我们采用inline函数:
template<typename T>
inline int Max(const T& a, const T& b){
return (a>b ? a:b);
}
这样就避免了前面宏替换被累加两次的问题.
总结:对于常量,原先写的宏用const或者enum来替换,宏函数用inline修饰的函数!
本节额外补充:const 有地址,enum与#define没有地址原因:
const 定义的实际是一个变量,const只限定它不能被修改,所有变量都可在程序运行时获取其地址
enum类型中的枚举项只是enum类型声明的一部分,它不是定义出来的变量,所以不能取地址
#define出来的是宏,它是预处理的东西,预处理后的编译阶段已经不存在,所以也不可能获取宏的地址
条款3:尽可能使用const关键字
这一节内容详细见下面:
https://github.com/Light-City/CPlusPlusThings/tree/master/const
条款4:确定对象被使用前已被初始化
(1)内置类型初始化
定义的时候我们一般这样写:
int a=0;
而不应该直接写
int a;
(2)类的初始化
用户自定义的类,我们需要构造函数初始化列表来完成此类的初始化.
A::A(const std::string& name, const std::string& address, conststd::list<PhoneNumber>& phones){
theName = name;
theAddress = address;
thePhones = phones;
}
函数里面是赋值而非初始化!正确方式如下:
A::A(const std::string& name, const std::string& address, conststd::list<PhoneNumber>& phones)
:theName(name),
theAddress(address),
thePhones(phones)
{}
(3)引用必须被初始化
int a = 1;
int& b = a;
(4)初始化no-local static对象
现有一个场景:在两个编译单元中,分别包含至少一个no-local static对象,当这些对象发生互动时,它们的初始化顺序是不确定的,所以直接使用这些变量,就会给程序的运行带来风险。
编译单元(translation unit): 可以让编译器生成代码的基本单元,一般一个源代码文件就是一个编译单元。
非本地静态对象(non-local static object): 静态对象可以是在全局范围定义的变量,在名空间范围定义的变量,函数范围内定义为static的变量,类的范围内定义为static的变量,而除了函数中的静态对象是本地的,其他都是非本地的。
回到问题,现有以下服务器代码:
class Server{...};
extern Server server; //在全局范围声明外部对象server,供外部使用
又有某客户端:
class Client{...};
Client::Client(...){
number = server.number;
}
Client client; //在全局范围定义client对象,自动调用了Client类的构造函数
以上问题在于,定义对象client自动调用了Client类的构造函数,此时需要读取对象server的数据,但全局变量的不可控性让我们不能保证对象server在此时被读取时是初始化的。试想如果还有对象client1, client2等等不同的用户读写,我们不能保证当前server的数据是我们想要的。
幸运的是一个小小的设计便可完全消除这个问题。唯一需要做的是:将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static) 。这些函数返回一个 reference 指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说, non-local static 对象被 local static 对象替换了。
解决方法: 将全局变量变为本地静态变量
使用一个函数,只用来定义一个本地静态变量并返回它的引用。因为C++规定在本地范围(函数范围)内定义某静态对象时,当此函数被调用,该静态变量一定会被初始化。
class Server{...};
Server& server(){ //将直接的声明改为一个函数
static Server server;
return server;
}
class Client{...};
Client::client(){ //客户端构造函数通过函数访问服务器数据
number = server().number;
}
Client& client(){ //同样将客户端的声明改为一个函数
static Client client;
return client;
}
参考资料:
Effective C++ 第3版
https://zhuanlan.zhihu.com/p/64141116