【effective c++读书笔记】【第2章】构造/析构/赋值运算(1)

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

1、如果你自己没声明,编译器就会为类声明(编译器版本的)一个拷贝构造函数,一个拷贝赋值操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会成为你声明一个默认构造函数。所有这些函数都是public且inline。惟有当这些函数被需要(被调用),它们才会被编译器创建出来。即有需求,编译器才会创建它们。

例如:

class Empty{};

就好像写下这样的代码:

class Empty{
public:
	Empty(){ ... }
	Empty(const Empty& rhs){ ... }
	~Empty(){ ... }
	Empty& operator=(const Empty& rhs){ ... }
};

2、编译器阻止产生拷贝赋值操作符的情况有以下几种:a、一个类中内含reference成员,b、一个类中内含const成员,c、如果某个基类将拷贝赋值操作符声明为private,编译器将拒绝为其继承类生成拷贝赋值运算符。

例子:

#include<iostream>
#include<string>
using namespace std;

template<typename T>
class NamedObject{
public:
	NamedObject(string& name, const T& value):nameValue(name),objectValue(value){}
private:
	string& nameValue;
	const T objectValue;
};

int main(){
	string newDog("Peter");
	string oldDog("Satch");
	NamedObject<int> p(newDog, 2);
	NamedObject<int> s(oldDog, 30);
	p = s; //error C2582: “operator =”函数在“NamedObject<int>”中不可用

	system("pause");
	return 0;
}

p=s;在VS 2013中报错,//errorC2582: “operator =”函数在“NamedObject<int>”中不可用

请记住:

  • 编译器可以暗自为类创建默认构造函数,拷贝构造函数,拷贝赋值操作符以及析构函数。

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

1、为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。

例子:

class HomeForSale{
public:
	...
private:
	...
	HomeForSale)(const HomeForSale&);
	HomeForSale& operator=(const HomeForSale&)
};

2、为驳回编译器自动(暗自)提供的机能,使用像Uncopyable这样的base class也是一种做法。

例子:

class Uncopyable{
public:
	Uncopyable();
	~Uncopyable();
private:
	Uncopyable(const Uncopyable&);
	Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale:private Uncopyable{
	...
};
请记住:

  • 为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

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

1、 C++明确指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义---实际执行时通常发生的是对象的derived成分没被销毁。这可是形成资源泄漏、败坏之数据结构、在调试器上消费许多时间。解决方法:给base classes定义一个virtual析构函数。

例子:

#include<iostream>
#include<string>
using namespace std;

class Base{
public:
	Base(){ cout << "base 构造" << endl; }
	~Base(){ cout << "base 析构" << endl; }
};

class Derived :public Base{
public:
	Derived(){ 
		p = new char[10];
		cout << "drived 构造" << endl; 
	}
	~Derived(){ 
		delete[] p;
		cout << "drived 析构" << endl; 
	}
private:
	char* p;
};

int main(){
	Base* p = new Derived();
	delete p;

	system("pause");
	return 0;
}


运行结果:

上面基类的析构函数没有声明为virtual,只有base析构函数被调用了,导致p申请的内存没有被释放,造成资源泄露。

#include<iostream>
#include<string>
using namespace std;

class Base{
public:
	Base(){ cout << "base 构造" << endl; }
	//~Base(){ cout << "base 析构" << endl; }
	virtual ~Base(){ cout << "base 析构" << endl; }
};

class Derived :public Base{
public:
	Derived(){ 
		p = new char[10];
		cout << "drived 构造" << endl; 
	}
	~Derived(){ 
		delete[] p;
		cout << "drived 析构" << endl; 
	}
private:
	char* p;
};

int main(){
	Base* p = new Derived();
	delete p;

	system("pause");
	return 0;
}
运行结果:

上面基类的析构函数声明为virtual,Derived析构函数和Base析构函数都被调用了, p申请的内存被释放,不会造成资源泄露。

2、任何类只要带有virtual函数都几乎确定应该也有一个virtual析构函数。如果一个类不含virtual函数,通常表示它并不意图被用做一个基类,当类不企图被当做基类的时候,令其析构函数为virtual往往是个馊主意。因为实现virtual函数,需要额外的开销(指向虚函数表的指针vptr)。

请记住:

  • 带有多态性质的基类应该声明一个virtual析构函数。如果一个类带有任何virtual函数,它就应该拥有一个virtual析构函数。
  • 一个类的设计目的不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数。  

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

1、C++并不禁止析构函数吐出异常,但它不鼓励你这样做。因为在析构函数中吐出异常可能会使程序过早结束或出现不明确行为。

2、当析构函数发生异常时,有以下2中解决办法:

a、如果抛出异常,就结束程序。通常通过调用abort完成。

DBConn::~DBConn(){
	try{ db.close(); }
	catch(…){
		制作运转记录,记下对close的调用失败;
		std::abort();
	}
}

b、吞下因调用close而发生的异常。

DBConn::~DBConn(){
	try{ db.close(); }
	catch(…){
		制作运转记录,记下对close的调用失败;
	}
}

3、如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。

请记住:

  • 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数(而非在析构函数中)执行该操作。  

版权声明:本文为博主原创文章,未经博主允许不得转载。

原文地址:https://www.cnblogs.com/ruan875417/p/4785448.html