《Effective C++》摘要

Date:
Categories: C++
Author: sysublackbear
Tags:

看了下《Effective C++》这本书,整理了下面的一些摘要。

Chapter0.导读

1.explict抑制隐式转换

例如:

class B {
public:
  explicit B(int x = 0, bool b = true);   //default构造函数,抑制隐私zh
}

2.理解拷贝构造和拷贝赋值的区别

class Widget {
public:
     Widget();                                                   //default构造函数
     Widget(const Widget& rhs);                    //copy构造函数
     Widget& operator=(const Widget& rhs);  //copy assignment操作符
     ...
};

Widget w1;          //调用default构造函数
Widget w2;          //调用copy构造函数
w1 = w2;               //调用copy assignment操作符
Widget w3 = w2;     //调用拷贝构造函数

Chapter1.让自己习惯C++

1.尽量以const,enum,inline替换#define

原因:宏对应的字面值或许从未被编译器看见;或许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称有可能没进入记号表内。这样会导致当你运用此常量但获得一个编译错误信息时,可能会带来困惑,因为这个错误信息也许会提到1.653而不是ASPECT_RATIO。如果ASPECT_RATIO被定义在一个非你所写的头文件内,你肯定对1.653以及它来自何处毫无概念,于是你将因为追踪它而浪费时间,这个问题也可能出现在记号式调试器中,原因相同:你所使用的名称可能并未进入记号表。 所以最好是用const,枚举和inline来替代宏。

2.指向常量的指针和常变量指针

const char p;   //non-const pointer, const data char const p;  //const pointer, non-const data

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

class TextBlock {
public:
     ...
     const char& operator[](std::size_t position) const
     {
          ...
          ...
          return text[position];
     }
     char& operator[](std::size_t position)   //只调用 const op[]
     {
          return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
     }
     ...
};

4.为内置型对象进行手工初始化,因为C++不保证初始化他们。

如:int x; 或者是类中成员

5.类中成员赋值

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

//下面的都是赋值,而不是初始化
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
{
     theName = name;
     theAddress = adress;
     thePhones = phones;
     numTimesConsulted = 0;
}

这不是最佳做法,C++规定,对象的成员变量的初始化发生在进入构造函数本体之前。而且这一种做法,效率不高,上面基于赋值的版本做法首先调用default构造函数为theName,theAdress,thePhones设初值,然后立刻再对它们赋予初值。default构造函数做的一切浪费了。 所有,应该用成员初值列来替代赋值。

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

6.为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static 对象。

Chapter2.构造/析构/赋值运算

1.编译器可以暗自为class创建default 构造函数,copy构造函数,copy assignment操作符,以及析构函数。惟有当这些函数被需要(被调用),它们才会被编译器创建出来

范例:

class Empty {
public:
     Empty() {  }               //default构造函数
     Empty(const Empty& hrs) {  }     //copy构造函数
     ~Empty() {  }   //析构函数
     Empty& operator=(const Empty& rhs) {  }  //copy assignment操作符
};

2.C++编译器拒绝Copy Assignment操作符的三种情况:

2.1.reference —— C++并不允许“让reference指向不同对象”;
NameObject<int> p(newDog, 2);
NameObject<int> s(oldDog, 3);

p = s;   //报错
2.2.同样,更改const成员也是不合法的。所以编译器不知道如何在它自己生成的赋值函数内面对他们。
2.3.如果某个base class(基类)将 copy assignment 操作符声明为private,编译器拒绝为其 derived classes 生成一个 copy assignment 操作符。毕竟编译器为derived class所生的copy assignment操作符想象中可以处理base class成分,但它们当然无法调用derived class无权调用的成员函数。编译器两手一摊,无能为力。

3.如果我们在设计的某些类里面不想编译器为我们做那么多“多余”的事情,比如暗自为我生成copy assignment操作和拷贝构造函数。即使我们不声明它,但是一旦有别人尝试调用你这个类,编译器都会自动为你声明并且创建它们。想要抑制编译器为我们自动声明并且创建这些方法,有两种做法:

a.将对应的成员函数声明为private并且不予实现。

class HomeForSale {
public:
     ...
private:
     ...
     HomeForSale(const HomeForSale& );    //只有声明
     HomeForSale& operator=(const HomeForSale&);    //参数非必要
};

一般而言,这种做法并非绝对安全,因为你的类里面的member和friend函数还是可以调用你的private函数的。除非你足够聪明,不去定义它们(仅仅是声明),那么如果某些人不慎调用任何一个,会获得一个连接错误。(这个用途用在了C++的标准程序库iostream中,你会发现标准程序库的ios_base, basic_ios和sentry,其copy构造函数和copy assignment操作符都被定义为private而且没有定义。

b.将连接期的错误移到编译器是好事来的,因为越早侦测出错误越好。 只要将copy构造函数和copy assignment操作符声明为private就可以办到,但不是在HomeSale自身,而是在一个专门为了阻止copying动作而设计的base class内。

class Uncopyable {
protected:
     Uncopyable() {
     ~Uncopyable() { }
private:
     Uncopyable(const Uncopyable&) ;       //但阻止copying
     Uncopyable& operator=(const Uncopyable&);
};

//为求阻止HomeForSale对象被拷贝,我们唯一选择的就是继承Uncopyable:
class  HomeForSale: private Uncopyable {
     ...
};

当想要调用的时候,编译器会尝试调用基类的对应函数,那些调用会被编译器拒绝。