Effective C++阅读记录(part 2 - 构造/析构/赋值运算 tips 5 - 8)

Tips05 - 了解C++默写并调用了哪些函数

当我们申明一个空类时,C++编译器会自动为我们什么一个copy构造函数、一个copy assignment操作符、一个析构函数以及一个默认构造函数。且这些函数都是public和inline的。
我们写的:

class Empty{};

编译器生成的:

class Empty{
public:
	Empty(){...} //默认构造函数
	Empty(const Empty& rhs){...} //copy构造函数
	~Empty(){} //析构函数

	Empty& operator=(const Empty& rhs){...} //copy assignment操作符
};

当我们进行如下操作时,它们才会被编译器创建。

Empty e1;
Empty e2(e1);
e2 = e1;

ps:编译器产生的析构函数时非虚函数,除非该类的基类自身声明有虚析构函数。

对于copy构造函数和copy assignment,在某些情况下,编译器会拒绝执行为它们创建的这些函数(如果它们被调用的话)。
比如:

template<class T>
class NamedObject{
public:
	NamedObject(std::string& name, const T& value);
	...
private:
	std::string& nameValue; //引用
	const T objectValue; //常量类型
}

std::string newDog("persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 35)
p = s;
;

由于缺少copy构造函数,系统会自动为它们创建一个默认的。
在p = s 之前,p.nameValue 和 s.nameValue 分别指向newDog和 oldDog。但当p = s时,如果这条操作执行,那么p.nameValue就会指向s.nameValue所指向的那个值,这样就改变了引用自身,这种操作在C++中是不能执行的。所以在这种情况下,C++的响应是拒绝编译赋值那一行。
const成员同理,在C++中,const的成员是无法改变的,所以C++也是无法执行的,所以只能自己定义copy assignment操作符。
最后就是如果某个base class将copy assognment 声明为private 编译器将拒绝为其derived class 生成一个copy assignment操作符。

**记住:**编译器可以暗自为class创建默认构造函数、copy构造函数、copy assignment操作符以及析构函数。

Tips06 - 如果不想使用编译器自动生成的函数,就应该明确拒绝

由tips05可以知道,如果在声明类的时候没有提供上述几个函数,那么系统会自动为它们创建,所以我们需要自己创建这些函数。如果你想阻止这些操作的发生,可以将它们声明为private,这样既能防止编译器自动生成,又能阻止人们调用它们。
但这样做的问题在于,成员函数 和 友元函数是可以调用它们。所以更好的办法就是:将这些成员函数声明为private而且不去实现它们。
比如:

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

有了上述class定义,当客户企图拷贝HomeForSale对象,编译器会阻止他,但如果在member函数或friend函数之内这么做,那么连接器就会报错。
只需要将copy构造函数和copy assignment操作符声明为private就可以办到,但不是在HomeForSale自身,而在一个专门为了阻止copy动作而设计的base class内。

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

class HomeForSale: private Uncopyable{...};

当友元或者member函数尝试拷贝HomeForSale对象时,编译器会尝试生成一个copy构造函数和一个copy assignment操作符,但这种调用会被拒绝,因为base class 的拷贝函数时private的。

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

Tips07 - 为多态基类声明virtual析构函数

class TimeKeeper{
public:
	TimeKeeper();
	~TimeKeeper();
	...
};
class AtomicClock: public TimeKeeper{...};
class WaterClock: public TimeKeeper{...};
class WristWatch: public TimeKeeper{...};

TimeKeeper* getTimeKeeper(); //返回一个指针,指向一个TimeKeeper派生类的动态分配对象
TimeKeeper* ptk = getTimeKeeper();
...
delete ptk;

该段代码的问题在于getTimeKeeper返回的指针指向一个derived class对象,而那个对象却经过一个base class指针被删除,而目前这个base class 的析构函数是个non-virtual。这会导致在实际执行时该对象的derived的成分没有被销毁。这是一个局部销毁对象。很可能导致资源泄漏、败坏等问题。
解决这个问题最简单的做法,就是把base class定义为virtual析构函数。

class TimeKeeper{
public:
	TimeKeeper();
	virtual ~TimeKeeper();
	...
};

需要注意的的:如果一个class不被企图当作base class时,让其析构函数成为virtual是一个馊主意,因为这会导致增加额外的开销。
因此,无端地将所有class地析构函数声明为virtual,就像从未声明它们virtual一样,都是错误的。许多人的心得是:只有当class内至少含有一个virtual函数,才为它申明virtual析构函数。
**注意:**polymorphic(带有多态性质的)base class应该声明一个virtual析构函数,如果class有用任何virtual函数,它就应该拥有一个virtual析构函数。
Class的设计目的如果不是作为base class使用,或不是为了具备多态性,就不应该声明virtual析构函数。

tips08 别让异常逃离析构函数

C++并不进制析构函数吐出异常,但它不鼓励这么做。理由如下:

class Widget{
public:
	...
	~Widget(){...}//假设可能会吐出异常
};
void doSomething()
{
	std::vector<Widget>v;
	...//v被自动销毁
}

当v被销毁,会自动销毁所有的Widgets,假设有10个Widgets,析构第一个时抛入异常,其他九个还是应该销毁,但如果在销毁后面9个时继续抛出异常。现在有两个同时抛出异常,这对于C++来说太多了。在两个异常同时存在时,程序弱不是执行结束就是导致不明确行为。
**记住:**析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能会抛出异常,析构函数应该捕获任何异常,然后吞下它们或者结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值