Effective C++ 05 ~ 12 条款 构造,析构,赋值(了解C++默默编写并调用哪些函数 若不想使用编译器自动生成的函数,就该明确拒绝 为多态基类声明virtual析构函数)

条款05 - 了解C++默默编写并调用哪些函数

C++ 会默认声明copy构造函数,copy assignment操作符(赋值),析构函数,构造函数,当自己手工实现时,编译器则不会创建。

缺点 :

copy构造函数,copy assignment操作符(赋值)都是浅拷贝(只复制指向某个对象的指针而不复制对象本身),所以当需要开辟新空间时,应该手工创建

条例06 - 若不想使用编译器自动生成的函数,就该明确拒绝

1.将函数设置为private即可阻止编译器为你创建

缺点:

不够安全,因为member函数和friend函数还是可以调用

2.利用一个 base class 类,然后让指定类继承这个 base 类

当尝试拷贝 HomeForstyle 对象的时候,编译器会调用基类的拷贝构造和赋值运算符函数,由于是 private 所以会被拒绝

class UncopyAble{
protected:
	UncopyAble(){

	}
	~UncopyAble(){}
private:
	UncopyAble (const UncopyAble& u);
	UncopyAble& operator=(const UncopyAble& u);
};

class HomeForstyle:public UncopyAble{
	//...
};

条款07:为多态基类声明virtual析构函数

1.带有多态性质base class 应该声明一个virtual 析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual 析构函数。

问题 :

当base class(基类)类型的指针指向derived class(派生类)被删除时,其对象的derived成分没被销毁 ,造成一个诡异的“局部销毁”对象,形成资源泄漏、败坏之数据结构

解决方法:

给base class一个virtual析构函数,此后删除derived class 对象,就会销毁整个对象,包括所有的derived class成分。

2.class 的设计不是作为base class使用,或不是为了具备多态性,就不该声明virtual 析构函数。

virtual 函数实现

  • 欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪个virtual函数该被调用
  • 这份信息通常是由一个所谓vptr(virtual table pointer)指针指出
  • vptr指针指向一个由函数指针构成的数组,称为vtbl(virtual table)
  • 每一个带有virtual函数的class 都有一个对应的vtbl
  • 当对象调用某一个virtual函数,实际被调用的函数取决于该对象的vptr指针所指的那个vtbl,编译器在其中寻找适合的函数指针。

使用virtual 析构函数的缺点

  • 对象内存增加,对象内增加vptr指针内存
  • 不再具有移植性。该对象也不再和其他语言(如C)内的相同声明有着一样的结构(其他语言没有vptr) ,因此也就不再可能把传递至其他语言所写的函数,除非明确补偿vptr。

pure virtual 析构函数

pure virtual 函数导致 abstract(抽象)class,不能被实体化的class,不能为那种类型创建对象。

条款08:别让异常逃离析构函数

1. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序。

问题:

考虑以下代码

class Widget{
	public :
	~Widget(){....};
};
void LRF(){
	std:: vector<Widget> v;
}

当vector v 被销毁时,当第一二个Widget销毁都出现了异常,就会导致不明确行为,本例中的任何其他容器都会出现相同情况,即使他们并非遇上严重的问题

解决:

吞下异常

class Widget{
	public :
	void close(){
		W.close();
		closed = ture;
	}
	~Widget(){
		if(closed) return ;
		try{
			close();
			//销毁
		}
		catch(....){
			//记下来
		}
	};
	private :
	Widget W;
	bool closed;
};
void i

2. 如果可会需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

条款09:绝不在构造和析构过程中调用virtual函数

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。

或者说是,在base class 构造期间,virtual函数不是virtual 函数

原因(直接)

当base class 构造时,derived class 尚未初始化

原因 (根本)

在derived class对象的base class 构造期间,对象的类型是base class 而不是derived class,不止时virtual 函数,运行期类型信息(条例27)也会被编译器看作base class,对象在derived class构造函数开始执行前不会成为一个derived class对象,同样的道理也适用于析构函数

条款10:令operator= 返回一个 reference to *this(返回地址)

int x ,y , z;
x = y = z  = 666;
// 等同 x = (y = (z = 666)) ;

为了实现连续赋值,赋值操作符必须返回一个reference 指向操作符的左侧实参

在 operator = 中处理 “自我赋值”

1. 确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap.

2. 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

问题:

class Bitmap{ ... };
class Widget { ...
	private: Bitmap* pb; // 指针,指向一个从heap分配的对象
}
Widget& Widget::operator=(const Widget& rhs) // 不安全的自我赋值
{
	delete pb; // 停止使用当前的Bitmap 
	pb = new Bitmap(*rhs.pb); // 使用rhs的Bitmap副本 return *this; 
}

此时如果直接销毁pb,那么可能会出现pb和rhs指向的是同一个对象。此时销毁
的不只是当前对象的Bitmap,也会销毁rhs的Bitmap,最后发现Widget内持有一个指针指向一个已被删除的对象

解决A(仍存在安全性问题):

operator= 函数内的最前面的加一个“证同测试”(identity test),达到“自我赋值”的检验目的

Widget& Widget::operator=(const Widget& rhs) 
{ 
	if (this == &rhs) // 证同测试:
	return *this; // 如果是自我赋值,就直接返回*this
	delete pb; 
	pb = new Bitmap(*rhs.pb); 
	return *this; 
}

但是上述代码的方法仍然存在异常方面的麻烦,如果“new Bitmap”导致异常 (不论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap

解决B(具备异常安全性):

原则 : 只需要注意在复制pb所指东西之前别删除pb

Widget& Widget::operator=(const Widget& rhs)
{
	Bitmap* pOrig = pb; // 记住原先的pb
	pb = new Bitmap(*rhs.pb); // 令pb指向*pb的一个副本
	delete pOrig; // 删除原先的pb
	return *this;
}

解决C(具备异常安全性和自我赋值安全): copy and swap 技术(异常安全性详情见条款29)

class Widget
{
	void swap(Widget& rhs); // 交换*this 和 rhs 的数据
};

Widget& Widget::operator=(const Widget& rhs)
{
	Widget temp(rhs); // 为rhs数据制作一份副本
	swap(temp); // 将*this数据和上述副本的数据进行交换
	return *this;
}

将“copying 动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更有效的代码

条款12:复制对象时勿忘其每一个成分

1. copying函数应该确保复制“对象内所有成员变量”及“所有base class 成分”

确保复制local内所有成员变量

设计良好之面向对象系统会将对象的内部封装起来,只留两个函数负责对象拷贝,copy构造函数和copy assignment操作符,被称为copying函数
如果你自己声明coping函数,当你的实现代码必然出错时编译器不会告诉你,因此如果你为class添加一个成员变量,你必须同时修改copying函数以及非标准形式的operator=

确保复制base class成员复制

任何时候只要你承担起“为derived class” 撰写copying,必须很小心的复制其base class成分

2. 不要尝试以某个copying函数实现另一个copying函数,应该将共同机能放进第三个函数中,并由copying函数共同调用

令某个copying函数调用另一个copying函数是不合理的

原因:

这就像试图构造一个已经存在的对象,可能造成对象败坏

反方向–copy构造函数调用copy assignment 操作符是无意义的

构造函数用来初始化新对象,而assignment 操作符只施行于已初始化对象身上,对一个尚未构造好的对象赋值,是错误的

安全消除copying构造函数和copy assignment操作符之间的代码重复的做法

建立一个新的成员函数给两者调用,这样的函数往往是private而且常被命名为init。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Effective C++》是由C++之父Bjarne Stroustrup所著的一本经典C++编程指南。这本书对于想要更深入理解和掌握C++编程的开发者来说是一本非常有用的资源。 《Effective C++》以52条编程规范的形式呈现,每一条规范都经过作者的亲身经历和经验总结。这些规范涵盖了C++中一些重要的概念、技术和最佳实践,从而帮助读者写出更高效、更健壮、更易维护的C++代码。 这本书主要分为多个部分,每个部分都聚焦于一个特定的主题。其中包括: 1. 构造/解构和赋值运算符重载:介绍了构造函数析构函数、拷贝构造函数赋值运算符的正确使用方式,避免内存泄漏和资源冲突。 2. 资源管理:提供了如何正确管理动态内存分配和资源使用的建议,包括智能指针、RAII等技术。 3. 类设计:讲解了类的设计原则和技巧,包括尽量使用const、规避对象切割等。 4. 继承与多态:介绍了如何正确使用继承和多态的技术,包括虚函数多态对象的销毁等。 5. 异常安全:提供了如何处理异常以及避免资源泄漏的方法。 通过阅读《Effective C++》,读者可以学习到许多编写高质量C++代码的技巧和实践。作者结合自己在C++设计与开发中的丰富经验,以清晰的语言和易于理解的示例,帮助读者深入理解C++语言的特性和问题,并提供了解决方案。对于想要进一步掌握C++开发者来说,这本书是一份不可或缺的参考资料。它不仅有助于提高代码质量,还能避免一些常见的陷阱和错误,从而使程序更加高效和可靠。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值