原创 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(++ab)<<endl;              // a被增加两次
cout<<MAX(++ab+10)<<endl;           // a被累加一次

这与我们的预期结果不同!

为了解决这个问题,我们采用inline函数:

template<typename T>
inline int Max(const T& aconst 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& nameconst std::string& addressconststd::list<PhoneNumber>& phones){
   theName = name;
   theAddress = address;
   thePhones = phones;
}

函数里面是赋值而非初始化!正确方式如下:

A::A(const std::string& nameconst std::string& addressconststd::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