Effective C++ 第二章——构造、析构、赋值运算

Effective C++ 第二章——构造、析构、赋值运算

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

1.编译器默认提供的函数
编译器为一个空类声明一个copy构造函数、一个copy assignment 操作符以及一个析构函数,若没有用户声明的构造函数,则编译器会帮忙声明一个default构造函数,这些函数均是public且inline。

class Empty {};
//等价于如下代码
class Empty1
{
public:
	Empty(){};
	Empty(const Empty& rhs) {...};
	~Empty(){};
	Empty& operator=(const Empty& rhs) {...};
};

当上述函数被需要时,编译器才会创建。编译器自己产生的析构函数是non-virtual,除非这个类有一个基类且基类的析构函数是virtual。

2.类对象中含有reference或const的成员变量的情况
类对象中含有reference或const的成员变量时编译器不提供copy assignment操作符,需要用户自己定义copy assignment操作符.

3.基类的copy assignment操作符为private的情况
基类的copy assignment操作符为private时,编译器拒绝为derived class生成一个copy assignment操作符。

请记住:

  1. 编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符以及析构函数。


条款6——若不想使用编译器自动生成的函数就应该明确拒绝

如果你希望你定义的类不允许对象的复制(即使你不定义copy构造函数、copy assignment操作符,编译器也会为你定义,这时你还是可以进行复制操作),你可以将copy构造函数、copy assignment操作符声明为private,这样外部就无法调用了,但是member函数和friend函数可以调用他们;你可以定义一个基类,将基类的copy构造函数、copy assignment操作符声明为private,然后再用基类派生出它。

  1. 用相应的成员函数声明为private并且不予以实现的方法
class HomeForSale {
public:
	...
private:
	HomeForSale(const HomeForSale&);
	HomeForSale& operator=(const HomeForSale&);
};
  1. 用继承的方法
class Uncopyable{
protected:
	Uncopyable() {};
	~Uncopyable() {};
private:
	Uncopyable(const Uncopyable&);
	Uncopyable& operator=(const Uncopyable&);
};
//再用Uncopyable基类来派生出你不想要能够进行copy操作的类
class HomeForSale: private Uncopyable{
};

请记住:

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


条款7——为多态基类声明virtual 析构函数

  1. 用基类指针指向一个派生类对象时,必须为基类定义一个virtual析构函数(不然调用完派生类后,该基类指针只销毁了基类的成员,而没有销毁派生类对象的成员,这会产生问题)。
  2. 如果一个类不需要当基类时,不要定义virtual析构函数;如果定义了会真大类对象的空间,是由于此时的类对象需要含有一个vptr (virtual table pointer,虚表指针);
  3. 不要将标准容器(string、vector、list、set、unordered_map)及其他任何带有“non-virtual析构函数”的类作为基类。
  4. 如果你希望将那个类抽象(不能被实体化的calss),可以将析构函数声明为一个pure virtual 析构函数,并提供一个定义。
class AWOV{
public:
	virtual ~AWOV() = 0;
};
//提供一个定义
AWOV::AWOV(){};
  1. 给基类一个virtual析构函数只适用于带多态性质的基类,目的是为了用base class的指针或引用处理derived class对象。

请记住:

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


条款8——别让异常逃离析构函数

C++不建议用析构函数吐出异常。

请记住:

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


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

class Transaction{
public:
	Transaction();
	virtual void logTransaction() const = 0;
};
Transaction::Transaction()
{
	...
	logTransaction();
}

class BuyTransaction : public Transaction
{
public:
	virtual void logTransaction() const;
};

class SellTransaction : public Transaction
{
public:
	virtual void logTransaction() const;
};

BuyTransaction b;

当上述调用BuyTransaction b;语句时,首先调用base class的构造函数,这个时候调用的是Transaction中的logTransaction()函数,而不是BuyTransaction中的logTransaction()版本,等based class中的成员构造完后才调用derived class的构造函数。
原因是:
       在derived class对象的base class构造期间对象类型是base class而不是derived class;不只virtual函数会被编译器解析到base class ,若使用运行期类型信息(runtime type information,例如dynamic_cast和typeid),也会被视为base class;对象在derived class的构造函数执行之前不会成为一个derived class对象。
        析构函数也一样;一旦derived class被析构,derived class内成员变成未定义;进入base class的析构函数后,就变成了一个base class对象。

请记住:

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


条款10——令operator=返回一个reference to *this

为了实现“连锁赋值”(x=y=z=15;),赋值操作符必须返回一个reference指向操作符的左侧实参。在class设计时也应该如此:

class Widget{
public:
	...
	Widget& operator=(const Widget& rhs)
	{
		...
		return *this;//返回一个引用
	}
	//其他
	Widget& operator+=(const Widget& rhs)//同样适用于+=、-=、*=等等
	{
		...
		return *this;//返回一个引用
	}
	Widget& operator=(int rhs)//这种也适用,即使操作符的参数类型不符合协定
	{
		...
		return *this;//返回一个引用
	}
};

该条款不仅适用于标准赋值形式,也适用于所以赋值相关运算。

请记住:

  • 令赋值(assignment)操作符返回一个reference to *this;


条款11——在operator中处理“自我赋值”

如果一个类中有一个指针指向一块动态分配的区域,当你自我赋值时可能会出现delete了本身同时也delete传入的参数。
传统做法是在operator=最前面加一个“证同测试”,但是会降低程序的效率。

class Bitmap{...};
class Widget{
	...
private:
		Bitmap* pd;
};
Widget& Widget::operator=(const Widget& rhs)
{
	if(this == &rhs) return *this;
	delete pb;
	pd = new Bitmap(*rhs。pb);
	return *this;
}

一个更好的办法是使用所谓的copy and swap技术。

class Widget{
...
void swap(Widget& rhs);//交换*this和rhs的数据;
...
};
Widget& Widget::operator=(const Widget& rhs)
{
	Widget temp(rhs);
	swap(temp);
	return *this;
}

请记住:

  • 确保当对象自我赋值时operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序以及copy and swap技术。
  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。


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

    负责复制的函数有两个:copy构造函数和copy assignment 操作符,统称为copying函数。
    若用户没有定义的话,编译器会为我们生成一个编译器版copying函数,其行为:将被拷贝对象的所有成员变量都做一份拷贝。
    如果你不喜欢编译器为你提供的copying函数的话(或者说是含有一个指向堆区的指针无法使用时),你需要自己定义你的copying函数,这是你需要保证class中的每个成员的正确赋值。当你需要为derived class实现copying函数时,你必须对base class部分也进行复制,这些成分往往是私有的,无法访问,你需要让你的erived class的copying函数调用base class函数。

class Customer{
public:
	...
	Customer(const Customer& rhs);
	Customer& operator=(const Customer& rhs);
private:
	std::string name;
};
class PriorityCustomer{
public:
	PriorityCustomer(const PriorityCustomer& rhs);
	PriorityCustomer& operator=(const PriorityCustomer& rhs);
private:
	int priority;
}
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) : Customer(rhs),
priority(rhs.priroity)
{
}
PriorityCustomer::PriorityCustomer& operator=(const PriorityCustomer& rhs)
{
	Customer::operator=(rhs);
	priority=rhs.priroity;
	return *this;
}

可以看到copy assignment操作符与copy构造函数往往有相同的实现部分,但是不允许用copy assignment操作符调用copy构造函数,反之copy构造函数调用copy assignment操作符也是不允许的。为了精简代码,你可以将他们公共的部分用另一个成员函数代替,给两者调用,这个函数往往是private,常常明明为init。

请记住:

  • Copying函数应该确保赋值“对象中的所有成员变量”及“所有base class成分”;
  • 不要尝试以某个copying函数实现另一个copying函数。应将共同机能放到第三个函数中,并由两个copying函数调用。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值