effective C++ 学习笔记(二)
04 确定对象被使用前已先被初始化
原因:
- 未初始化的值会导致不明确的行为
永远在使用对象之前先将初始化:
对于内置类型,必须进行手工完成
对于非内置类型以外,初始化责任落在构造函数身上:确保每个构造函数都将对对象的每个成员进行初始化化。
赋值与初始化
class A{
public:
A(const int &n);
~A();
private:
int x;
int y;
};
A::A(const int &n)
{
//赋值并非初始化
x=n;
y=n;
}
A::A(const int &n):x(n),y(n)
{//初始化,通常效率比较高,按其声明的次序进行初始化
}
C++规定,对象成员变量的初始化动作发生在进入构造函数本体之前。
编译器总是会为用户自定义类型之成员变量自动调用默认构造函数-如果在成员初值列中没有被指定初值。
为了避免漏掉某些必须初值的成员变量,于是有:
- 总是在初值列中列出所有的成员变量。
对于不同编译单元内订阅的non-local static 对象的初始化对象,在c+对其初始化次序并无明确的定义;
那么为了解决某个对象必须另一个类对象使用其之前已经初始化完成,可以将non-local-static对象搬运到自己专属的的函数中。
FileSystem &tfs()
{
static FileSystem fs;
return fs;
}
//在类一个另类中可以 调用fs()方法获取一个静态tfs对象
总结:
- 为内置型对象进行手工初始化,因为c++不保证初始化他们;
- 构造函数最好使用成员初始列,效率更高;
- 为了消除“跨编译单元初始化次序”,可以使用local static对象替代non-static对象;
05 了解c++默认编写并调用那些函数
在一个空类,当这些函数并调用时,才会被创建出来
- default构造函数
- copy构造函数
- 析构函数
- 默认重载赋值运算符函数
06 若不想使用编译器自动生成的函数,就该明确的拒绝
拒绝copying操作,可以将copy构造函数和默认重载赋值运算符函数声明为private,但不能防止在public的成员函数中错误的使用他们。
所以有一个专为为阻止copying错误设计的方法
class Uncopyable{
protected:
Uncopyable(){};
~Uncopyable(){};
private:
Uncopyable(const Uncopyable&);
Uncopyable& operatpr=(const Uncopyable&);
};
class HomeForsale:private Uncopyable{};
//当想要copy HomeForsale时,会被拒绝
总结:为了驳回编译器自动的功能可以将相应的成员函数声明为private不给予实现。
07 为多态基类声明的virtual析构函数
当一个派生类对象经由一个基类指针删除,而基类带有非虚的析构函数,则通常会发生派生类中的派生部分并没有被销毁。
消除该问题的方式,这是在基类中使用virtual析构函数。
则在设计一个类时,可以以是否设计virtual函数来确定该类是否意图作为基类。
带有虚函数的类,会再类中创建一个虚函数表,会导致类的体积增加。那么如果该类不作为基类,则不需使用虚函数。
总结:
多态的基类应该声明一个virtual析构函数。如果class中带有任何的virtual函数,它必须拥有一个virtual析构函数。
08 别让异常逃离析构函数
如果在析构函数函数吐出异常,程序也可能过早结束或出现不明确行为。
如果在析构函数中必须执行一个动作,而该动作可能会发生异常:
以下方法可以避免问题:
- 在程序块中使用用try…catch
//close抛出异常就结束程序
A::~A()
{
//关闭一个数据库连接流db
try{db.close();}
catch(...)
{
...
std::abort();
}
}
//若不适应abort(),则会吞下异常,这通常是个坏主意
但上述方法都不会对异常作出反应。
一个较佳的方法是,设计一个新的接口对可能出现的问题进行反应:
class A
{
...
public:
void close()
{
db.close();
closed=true;//管理db是否已经被关闭
}
~A()
{
if(!closed)
{//用老套路关闭流
try{db.close();}
catch(...){
...
std::abort();}
}
};
总结:
- 析构函数绝对不能吐出异常。
- 如果需要对某操作运行期间出现的异常进行反应,那么使用一个普通的函数进行操作。