Effective C++ 读书笔记4

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

如果你声明自己的拷贝函数,意思是告诉编译器你不喜欢缺省实现中的某些行为。编译器仿佛被冒犯似得,会以一种奇怪的方式回敬:当你的实现代码几乎必然出错时却不告诉你:

void logCall(const std::string& funcName);
class Customer{
	
public:
	//...
	Customer(const Customer& rhs);
	Customer& operator=(const Customer& rhs);
	//..
private:
	std::string name;
};

Customer::Customer(const Customer& rhs):name(rhs.name){
	logCall("Customrer copy constructor");
}
Customrer::Customrer::operator=(const Customrer& rhs){
	
	logCall("Customer copy assignment operator");
	name = rhs.name;
	return *this;
}

当另一个新成员变量加入后,如果程序员没有把该新成员变量在拷贝赋值和拷贝构造中处理,大多数编译器不会警告。发生继承时,可能会造成此一主题最暗中肆虐的一个潜在危机:

class PriorityCustomer: public Customer{
public:
	//...
	PriorityCustomer(const PriorityCustomer& rhs);
	PriorityCustomer& operator=(const PriorityCustomer& rhs);
	//...
private:
	int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):priority(rhs.priority){
	
	logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=(const PriorityCustomer& rhs){
	logCall("PriorityCustomer copy assignment operator");
	priority = rhs.priority;
	return *this;
}

以上两个实现复制了PriorityCustomer声明的成员变量,但每个PriorityCustomer还内含它所继承的Customer成员变量复件,而那些成员变量却未被复制。所以任何时候只要你承担起为派生类撰写拷贝函数的重大责任,必须很小心的也复制其基类成分。那些基类成分往往是private,所以无法直接访问,应该让派生类的拷贝函数调用积累的拷贝函数:

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs),priority(rhs.priority){
	
	logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=(const PriorityCustomer& rhs){
	logCall("PriorityCustomer copy assignment operator");
	Customer::operator=(rhs);
	priority = rhs.priority;
	return *this;
}

请记住:

1.拷贝函数应该确保复制对象内的所有成员变量及所有基类成分。

2.不要尝试某个拷贝函数实现另一个拷贝函数,应该将共同技能放进第三个函数中,并油两个拷贝函数共同调用。


资源管理

所谓资源就是,一旦用了它,将来必须还给系统。如果不这样,槽糕的事情就会发生。C++程序中最常用的资源就是动态分配内存,但内存只是你必须管理的众多资源之一。其他常见的资源还包括文件描述符、互斥锁、图形界面中的字形和笔刷、数据库连接、网络socket。

条款13: 以对象管理资源

class Investment{
	
};

Investment* CreateInvestment();

void f(){
	
	Investment* pInv = CreateInvestment(); //factory function
	//...
	delete pInv;
}

某些情况下f可能无法删除它得自CreateInvestment的投资对象,因为可能有return语句,可能在delete之前有异常。所以为了确保CreateInvestment返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f,该对象的析构寒暑会自动释放那些资源:把资源放进对象内,我们便可依赖C++的析构函数自动调用机制确保资源被释放。

标准库的auto_ptr是个类指针对象,即只能指针,其析构函数自动对其所指对象调用delete:

void f(){
	
	std::auto_ptr<Investment> pInv(CreateInvestment());
}

这个例子示范“以对象管理资源”的两个关键想法:

1.获得资源后立刻放进管理对象内。

2.管理对象运用析构函数确保资源被释放。

由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一对象,这样对象会被销毁多次。为了预防这个问题,auto_ptr规定:若通过拷贝构造函数或者拷贝赋值操作符复制它们,它们会变成null。但是STL容器要求其元素能够复制,因此这些容器容不得auto_ptr。

auto_ptr的替代方案是引用计数型智慧指针,可以持续追踪共有多少个对象指向某笔资源,当无人指向它们时自动删除该资源:

void f(){
	
	std::tr1::shared_ptr<Investment> pInv(CreateInvestment());
}

由于shared_ptr的复制行为一如预期,它们可以被用于STL容器。


auto_ptr和tr1::shared_ptr在其析构函数内做的是delete而不是delete[],意味着在动态分配而得的array身上使用auto_ptr和shared_ptr是个馊主意,但是编译不会有问题:

std::auto_ptr<std::string> aps(new std::string[10]);//错误,会用上错误的delete
std::tr1::shared_ptr<int> spi(new int[1024]);

并没有特别针对C++ 动态分配数组而设计的类似auto_ptr或者shared_ptr那样的东西。那是因为vector和string几乎总是可以取代动态分配而得的数组。但是boost库中的boost::scoped_array和boost::shared_array class可以提供这些行为。


请记住:

1.为防止资源泄漏,请使用RAII对象,他们在构造函数中获得资源并在析构函数中释放资源。

2.两个常被使用的RAII分别是tr1::shared_ptr和auto_ptr,前者通常是较佳选择,因为其拷贝行为比较直观。若选择auto_ptr,复制动作会使它指向null


条款14:在资源管理类中小心copying行为

RAII(Resource Acquisition Is Initialization)是资源管理类的脊柱。但是并非所有的资源都是heapbased,对于这种资源,auto_ptr和shared_ptr这样的智能指针往往不适合作为资源掌管者,此时需要建立自己的资源管理类。例如,假设我们使用Mutex的互斥器,共有lock和unlock两个函数可用:

void lock(Mutex* pm);

void unlock(Mutex* pm);

建立一个类来管理锁:

class Lock{
public:
	explicit Lock(Mutex* pm): mutexPtr(pm){
		
		lock(mutexPtr);
	}
	
	~Lock(){
		
		unlock(mutexPtr);
	}
private:
        Mutex *mutexPtr;
 };
如果Lock对象被复制,会发生什么事???????(作者没解释)

可以有以下两种选择:

1.禁止复制:许多时候允许RAII对象被复制不合理。应该禁止复制,Lock类就是如此。

2.对底层资源祭出“引用计数法”。有时候希望保有原始资源,直到它的最后一个使用者被销毁。可以使用shared_ptr,所以mutexPtr需要从Mutex* 改为shared_ptr<Mutex>。然而很不幸,shared_ptr的缺省行为是当引用次数为0时删除其所指物,不是我们要的行为,我们只是想解除锁定。幸运的是shared_ptr允许指定所谓的deleter,那是一个函数或者函数对象,当引用次数为0时被调用:

class Lock{
public:
	explicit Lock(Mutex* pm):
	mutexPtr(pm, unlock){              //unlock函数作为删除器
		lock(mutexPtr.get());
	}
private:
	std::tr1::shared_ptr<Mutex> mutexPtr;
};

3.复制底部资源:需要资源管理类的唯一理由是,当你不再需要某个复件时确保它被释放。在此情况下复制资源管理对象,应该同时也复制其所包含的资源,即要进行深度拷贝。

4.转移底部资源的拥有权:例如auto_ptr的复制,会把原始对象中的资源置为null。


请记住:

1.复制RAII对象必须一并复制它所管理的资源,所以资源的拷贝行为决定RAII对象的拷贝行为。

2.普遍而常见的RAII类拷贝行为是:抑制拷贝,施行引用计数法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值