(05)了解C++默默编写并调用那些函数
默认构造函数,析构函数,拷贝构造函数和赋值操作符。注意:
(1)只有当这些函数被调用时,它们才会被编译器创建出来。
(2)编译器创建的这四个函数都是public且inline的。
(3)编译器生成的析构函数是non-virtual的,这很容易引起内存泄露 。
(4)默认构造函数和析构函数会做一些这样的工作:调用基类和非static成员变量的默认构造函数和析构函数。
(5)拷贝构造函数和赋值操作符,编译器只是单纯的将源对象的每一个非static成员变量拷贝到目标对象。内置类型按位复制,类类型调用该类的默认拷贝构造函数。
(6)(3)和(4)可能不合法,比如对象A中和对象B中都有引用,如果想合法需要自定义拷贝构造函数和赋值操作符。ps:当一个类中有引用和const成员变量时,要在类的初始化列表中初始化。
(06)若不想使用编译器自动生成的函数,就应该明确拒绝
禁止复制:
(1)将拷贝构造函数和赋值操作符声明为private,并且没有实现,
(2)class Uncopyable
{
protected:
Uncopyable(){...}
~Uncopyable(){...}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operater=(const Uncopyable&);
}
(07)为多态基类声明析构函数
(1)析构函数如果不为virtual,则CBase* p = new CDerive();delete p;子类对象部分不会被释放,造成内存泄露。
(2)任何不以多台为目的的虚函数,都是闲的蛋疼,因为这增加虚表指针的额外内存。反过来讲,假如一个类中有虚函数,那么就是为了使用多态技术,那么就会有内存泄露的风险,所以析构函数需要设为virtual。
(3)标准库中的一些类析构函数没有设置为虚函数,比如string,假如继承string,那么很可能引起内存泄露。
(4)有时候一个类带有一个纯虚析构函数,会比较方便。抽象类,比如用于工厂模式时候
class AWOV
{
public:
virtual ~AWOV() = 0;
}
(08)别让异常逃离析构函数
析构函数若吐出异常,程序可能过早结束或出现不明确行为。这可能引起内存泄露等问题。三种方法处理:
(1)如果析构函数抛出异常,则结束程序。这种方法阻止异常从析构函数中传播出去,从而抢先在“不明确行为”之前置程序于死地。
try
{
Release();
}
catch(...)
{
//记录析构失败
std::abort()
}
(2)吞下因调用release而发生的异常。这种方法是个坏主意,因为出现了异常发现不了。第一种方法可以通过终止程序来让程序员知晓异常的发生。
有时候吞下异常比负担“草率结束程序“或”不明确行为带来的风险“好。为了让着成为一个可执行方案,程序必须能继续可靠地执行,即便在遭遇并忽略一个错误之后。
try
{
Release();
}
catch(...)
{
//记录析构失败
}
(3)前两种都无法对抛出异常做出反应,也就是说都没有针对这种异常做处理。
(09)绝不在构造和析构中调用virtual函数
(一)问题:
(1)因为在基类构造函数执行完之前,子类构造函数是不会执行完的。换句话说,子类对象内的基类成分会在子类成分构造完成之前先构造完成。所以,在基类构造函数中调用虚函数时候,只会调用基类的函数,不会多态。
即:在derived class的base class构造期间,virtual函数不是virtual函数,编译器把此时的对象的类型当做base class。
(2)同上,进入derived class的base class析构函数后,对象就成为base class类型了。
(二)如何解决这个问题?
因为无法在构造期间无法使用虚函数从基类往下调用,所以可以从子类将必要的信息传递给基类而弥补。即基类中虚函数改为非虚函数,然后要求子类构造函数必须传递必要信息(在构造函数初始化列表中)给基类的构造函数。
(10)令operator=返回一个reference to *this
因为赋值操作符允许连锁赋值,即a = b = c;所以这要求赋值操作需要返回一个reference指向操作符的左侧实参
Widget& operator=(const Widget& rhs)
{
...
return *this;
}
这个协议不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算。
注意:这只是个协议,并无强制性。如果不遵循它,代码一样可以通过编译。然而这份协议被所有内置类型如string vector tri“”shared_ptr等共同遵守,所以不要标新立异。
(11)在operator=中处理自我赋值
在a = a时候,会先释放左a,然后拷贝右a至左a,但是拷贝前a已经被释放了,已经是野指针了。
解决办法:
(1)证同测试:具备自我赋值安全性,但不具备异常安全性
Widget& Widget::operator=(const Widget& rhs)
{
if(this==&rhs)
{
return *this;
}
delete p;
p = new Test(*rhs.p);
return *this;
}
(2)假如具备异常安全性,则往往具备自我赋值安全性。所以可以只考虑异常安全性,在自我赋值出异常时可以处理。
(3)删除之前先保存
Widget& Widget::operator=(const Widget& rhs)
{
Test* temp = p;
p = new Test(*rhs.p);
delete p;
return *this;
}
(4)copy and swap:暂略
(12)复制对象时勿忘其每一个成分:
(1)复制所有local成员变量,调用所有base classes内适当地copying函数
(2)另copy assignmen操作符调用copy构造函数是不合理的。反过来同样如此。