C++构造、析构和赋值

来源:互联网 发布:无线wifi网络电视 编辑:程序博客网 时间:2024/06/06 01:45

(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构造函数是不合理的。反过来同样如此。

0 0
原创粉丝点击