《Effective C++》摘要
看了下《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 {
...
};
当想要调用的时候,编译器会尝试调用基类的对应函数,那些调用会被编译器拒绝。