高效C++编程学习笔记(二)

条款05.默认构造函数/拷贝函数/赋值运算

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

需要注意的有以下几点:

  1. 对于拷贝与赋值操作,注意成员的浅拷贝与深拷贝。
  2. 对于引用与const类型的变量,拷贝与构造函数需要自己实现,默认生成的会报错

条款06.防止编译器使用默认的构造/拷贝/赋值函数

阻止拷贝与赋值操作可以将其声明为私有函数,代码如下:

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

也可以通过基类去实现,这种方法甚至杜绝了friend函数以及成员函数的调用,但是会导致多重继承等问题。

class Uncopyable{
protected:
	Uncopyable(){}
	~Uncopyable(){}
private:
	Uncopyable(const Uncopyable&);	
	Uncopyable& operator=(const Uncopyable&);
};

class HomeForSale: private Uncopyable{
...
};

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

以很常用的factory模式为例,时间基类

class TimeKeeper{
public:
	virtual ~Timekeeper();
	...
} ;
class AtomicClock: public TimeKeeper {...};	//原子表
class WaterClock:public TimeKeeper {...};	//水钟
class WristWatch: public TimeKeeper {...};		//腕表	

TimeKeeper* ptk = getTimeKeeper();	//getTimeKeeper()函数是用于继承体系中获得一个动态分配对象。
delete ptk;								//释放它,避免资源泄漏

可以看到基类的析构函数是virtual的,因为如果基类带着一个non-virtual的析构函数在派生类执行的时候会出现派生类的对象成分没被销毁。

纯虚函数可以将类转化为抽象类,用于多态,当没有任何纯虚函数时可以将析构函数申明成纯虚。

class AWOV{
public:
	virtual ~AWOV()=0;
};
//需要纯虚析构函数的定义
AWOV::~AWOV(){}	
  • (带有多态性质的)基类应该声明一个virtual析构函数,如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
  • Classes的设计目的不是作为base classes使用,或不是为了具备多态性(如上面的Uncopyable),就不该声明virtual析构函数。

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

举个实例,使用一个class负责数据库链接

class DBConnection{
public:
	...
	static DBConnection create();	//用于返回DBConnection对象;

	void close();	//关闭联机;失败则抛出异常
}

可以用一个类来管理DBConnection,防止忘记调用close

class DBConn{
public:
	...
	~DBConn()
	{
		db.close();
	}
private:
	DBConncetion db;
};

对于析构函数调用close产生异常有两种处理方式。

  • close抛出异常就结束程序。通常通过abort完成
DBConn::~DBConn()
{
	try{ db.close();}
	catch(...){
		制作运转记录,记下对close的调用失败
		std::abort();
	}
}
  • 吞下调用close而发生的异常
DBConn::~DBConn()
{
	try{ db.close();}
	catch(...){
		制作运转记录,记下对close的调用失败
	}
}

也可以重新设置DBConn接口,提供函数是的客户有机会对可能出现的问题作出反应。

class DBConn{
public:
	...
	void close()
	{
		db.close();
		closed = true;
	}
	~DBConn()
	{
		if(!closed)
		{
			db.close();
		}
		catch(...){
			上面两种异常的处理方式中的一种
		}
	}
private:
	DBConnection db;
	bool closed;
}

只是提供了一个能让客户处理异常的方式。

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

条款09.绝对不要在构造和析构函数中直接或者间接调用virtual函数

例如在一个对象创建时需要log记录。

class Transaction{
public:
	Transaction();
	virtual void logTransaction()const = 0;	//做出一份因类型不同而不同的日志记录
	...
};
Transaction::Transaction()
{
	...
	logTransaction();		//最后动作是记录这笔交易
}
class BuyTransaction:public Transaction{
public:
	virtual void logTransaction()const;	//记录(log)此型交易
	...
};

class SellTransaction:public Transaction{
public:
	virtual void logTransaction()const;	//记录(log)此型交易
};

BuyTransaction b;

当BuyTransaction被构造是,首先会调用基类的构造函数,此时虚构函数logTransaction会调用Transaction中的纯虚函数导致错误,及时是定义过的纯虚函数也会导致一系列的问题。析构函数同理。
对于如何解决这个问题的话可以如下所示:

class Transaction{
public:
	explicit Transaction(const std::string& logInfo);
	void logTransaction(const std::string& logInfo)const;//如今是个non-virtual函数
	...
};
Transaction::Transaction(const std::string& logInfo)
{
	...
	logTransaction(logfo);		//如今是个non-virtual调用
}
class BuyTransaction:public Transaction{
public:
	BuyTransaction(parameters)
	:Transaction(createLogString(parmeters))
	{...}
	...
private:
	static std::string createLogString(parameters);
};

条款10.在operator=返回一个reference to *this

为了实现对象的连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧实参,因此为class实现赋值操作是应该遵循:

class Widget{
public:
   ...
   Widget& operator=(const Widget& rhs)
   {
   	...
   	return* this;		//返回左侧对象
   }
}

同样operator+=也适用。

条款11.operator=中处理“自我赋值”

尽管自己对自己赋值看似很蠢,但是在C++中有别名的情况下还是可能会发生这种情况,举个自己给自己赋值时产生错误的情况。

class Bitmap{...};
class Widget{
...
private:
	Bitmap* pb;		//指针,指向一个从heap分配而得的对象
};
Widget& 
Widget::operator=(const Widget& rhs)
{
	delete pb;
	pb = new Bitmap(*rhs.pb);			//使用rhs bitmap的副本(复件)
	return *this;
}

上面在自我赋值前就已经释放动态内存了。可以如下修改

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

这段代码既能保持自我赋值也能具有“异常安全性”。另外一个替代方案是使用copy and swap技术

class Widget{
...
void swap(Widget& rhs);		//交互*this和rhs的数据
...
};
Widget& Widget::operator = (const Widget& rhs)
{
	Widget temp(rhs);		//rhs数据制作一份副本
	swap(temp);				//将*this数据和上述复件数据交换
	return *this;
}
对于参数传递方式
Widget& Widget::operator=(Widget rhs)
{
	swap(rhs);			//将*this的数据和复件/副本的数据交互
	return *this;
}

条款12.复制对象时不要忘记每一个成分

1、自定义的COPY构造函数和赋值操作符一定要却把本类中的所有成员,包括新增加的成员和父类的所有成员都要复制过来。
2、不要尝试COPY构造函数和赋值操作符进行互相调用。应该将共同机能放进第三个private函数,并由两个coping函数共同调用。

class Date{};
class Customer{
public:
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
private:
    std::string name;
    Date lastTransaction;
};
Customer::Customer(const Customer &rhs)
    :name(rhs.name),
      lastTransaction(rhs.lastTransaction)  //复制rhs的数据
{

}
Customer &Customer::operator=(const Customer &rhs)
{
    name = rhs.name;
    lastTransaction = rhs.lastTransaction;
    return *this;
}

class PriorityCustomer:public Customer{	//一个派生类
public:
	...
	PriorityCustomer(const PriorityCustomer& rhs);
	PriorityCustomer& operator=(const PriorityCustomer& rhs);
	...
	private:
		int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:Customer(rhs),
priority(rhs,priority)
{}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
	Customer::operator=(rhs);
	priority = rhs.priority;
	return *this;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值