Effective C++————习惯C++

Effective C++————习惯C++

2018, Dec 17    

条款01:视C++为一个语言联邦

将C++视为一个由相关语言组成的联邦而非单一语言。

  • C
  • Object-Oriented C++
  • Template C++
  • STL

条款02:尽量以 const,enum,inline 替换 #define

  • #define所使用的名称可能并未进入symbol table,调试增加困难
  • #define不重视作用域,不能提供封装性,无法利用#define创建一个class的专属常量
// static class常量 完成 in class 初值设定
//hpp
class GamePlayer{
    static const int NumTurns = 5; //常量声明
    int scores[NumTurns];
    ...
};
//cpp
const int GamePlayer::NumTurns; //NumTurns定义
//hpp
class CostEstimate{
private:
    static const double FudgeFactor; // staic class常量声明,位于头文件内
    ...
};

//cpp
const double CostEstimate::FudgeFactor = 1.35;  // 位于实现文件内
// enum hack
class GamePlayer{
    enum {NumTurns=5};  // enum hack方法令NumTurns成为5的记号
    int scores[NumTurns];
    ...
};
  • #define实现宏很容易出错,建议用template<> inline代替

条款03:尽可能使用const

char greeting[] = "Hello";
char* p = greeting; // non-const pointer, non-const data
const char* p = greeting;   //non-const pointer, const data
char* const p = greeting;   // const pointer,non-const data
const char* const p = greeting; // const pointer, const data

const 成员函数

bitwise const 以及 logical const,这部分较为复杂,查看书籍深入理解。

在const 和 non-const成员函数中避免重复

当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复

条款04:确定对象被使用前已先被初始化

  • 为内置数据对象进行手工初始化

  • 构造函数中最好使用成员初始化列进行初始化,而不要在构造函数本体内使用赋值操作。初始化列中的成员变量,其排列次序应该和它们在class中的声明次序相同

class PhoneNumber {...};
class ABEntry{
public:
    ABEntry(const std::string& name, const std::string& address,
    const std::list<PhoneNumber>& phones);
private:
    std::string theName;
    std::string theAddress;
    std::list<PhoneNumber> thePhones;
    int numTimesConsulted;
};

//ABEntry::ABEntry(const std::string& name, const std::string& address,
//    const std::list<PhoneNumber>& phones)
//{
//    theName = name;         //这些都是赋值assignments而非初始化initializations
//    theAddress = address;
//    thePhones = phones;
//    numTimesConsulted = 0;
//}

ABEntry::ABEntry(const std::string& name, const std::string& address,
    const std::list<PhoneNumber>& phones)
    :theName(name),         // initializations
    theAddress(address),
    thePhones(phones),
    numTimesConsulted(0)
{

}
  • 为免除’跨编译单元初始化次序’问题,以local static对象代替non-local static对象

假设有一单一文件系统类

class FileSystem{       // 来自程序库
public:
    ...
    std::size_t numDisks() const;
    ...
};
extern FileSystem tfs;  //预备给客户使用的对象

假设客户建立了一个class以处理文件系统内的目录,很自然他们的class会用上tfs对象

class Director{         //由程序库客户建立
public:
    Directory(params);
    ...
};
Directory::Directory(params)
{
    ...
    std::size_t disks = tfs.numDisks();     //使用tfs对象
    ...
}

客户决定创建一个Directory对象,用来放置临时文件

Directory tempDir(params);      //为临时文件

除非tfs在tempDir前被初始化,否则tempDir就会用到尚未初始化的tfs。但是tfs和tempDir由不同的人在不同的时间于不同的源码文件内建立起来,如何确保tfs会在tempDir前先被初始化呐?

可以将每个non-local static对象搬到自己专属函数内,这些函数返回一个reference指向它所含的对象,换句话说non-local static对象被local static对象替换了,这也是Singleton模式的常见手法

class FileSystem {...}; //同前
FileSystem& tfs()       //这个函数替换tfs对象
{
    static FileSystem fs;   //local static 对象
    return fs;              //返回一个reference指向上述对象
}
class Directory {...};  //同前
Directory::Directory(params)
{
    ...
    std::size_t disks = tfs().numDisks();
    ...
}
Directory& tempDir()
{
    static Directory td;
    return td;
}

这种方法很好的解决static对象初始化顺序问题

但是因为内含static对象,使得在多线程系统中带有不确定性,再说一次,任何一种non-const static对象,不论是local还是non-local,在多线程环境下等待某事发生都会出问题

引用

Effective C++改善程序与设计的55个具体做法(第三版)