【EffectiveC++】构造/析构/赋值运算-上篇

前言

几乎每一个写class都会有一个或多个构造函数、一个析构函数、一个copy assignment操作符。这些很难让你特别兴奋,比较他们是你的基本某生工具,控制这基础操作,像是产出新对象并确保他被初始化、摆脱旧对象并确保他被适当清理、以及赋予新对象值。如果这些函数犯错,会导致深远且令人不愉快的后果,遍及你整个classes。所以确保他们的行为正确是生死攸关的大事。

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

Empty e1;//default构造函数;
//析构函数
Empty e2(e1);//copy构造函数
e2=e1;//copy assignment操作符
好,我们知道了,编译器为你写函数,但这些函数做了什么呢?唔,default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用base classes和non-static成员变量的构造函数和析构函数。注意,编译器产出的析构函数是个non-virtual,除非这个class的base class自身声明有virtual析构函数(这种情况这个函数的虚属性;virtualness;主要来自base class)。

如果你打算在一个“内含reference成员”的class内支持赋值操作,你必须自己定义copy assignment操作符。面对“内含const成员”的classes,编译器反应也一样。更改const成员是不合法的,所以编译器不知道如何在它自己生成的赋值函数内面对他们。最后还要一种情况“”:如果某个base classes将copy assignmnet操作符声明为private,编译器拒绝为其derived classes生成一个copy assignment运算符。毕竟编译器为derived classes所生的copy assignment操作符想象中可以处理base classes成分,但当他们无法调用derived class无权调用的成员函数。编译器两手一摊,无能为力。
注意:编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符、以及析构函数。

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

禁止拷贝class一般的方法就是把拷贝构造函数和赋值函数定义为私有函数,一下摘自effective c++:

房地产代理商出售房屋,服务于这样的代理商的软件系统自然要有一个 class(类)来表示被出售的房屋:

class HomeForSale { … };

每一个房地产代理商都会很快指出,每一件房产都是独特的——没有两件是完全一样的。在这种情况下,为 HomeForSale object(对象)做一个 copy(拷贝)的想法就令人不解了。你怎么能拷贝一个独一无二的东西呢?因此最好让类似这种企图拷贝 HomeForSale object(对象)的行为不能通过编译。
HomeForsale h1;
HomeForsale h2;
HomeForsale h3(h1);//企图拷贝h1不通过编译
h1=h2;企图拷贝h2不通过编译
啊呀,阻止这一类代码的编译并不是很直观。通常如果你不希望class支持某一个特定的技能,只要不声明对应的函数就是了。但这个策略对copy构造函数和copy assignment操作符却起不了作用,因为条款五已经指出,如果你不声明他们而某些人们尝试调用他们,编译器会为你声明他们。
这把你逼到了一个困境。如果你不声明copy构造函数或copy assignment操作符,编译器可能为你产出一份,于是你的class支持copying。如果你声明他们,你的class还是支持copying。但这里的目标却要阻止copying!
答案的关键所在是,所以的编译器产出的函数都是public。为阻止这些函数被创建出来,你得自行声明他们,但这里并没有什么需求使你必须将它们声明为public。因此你可以将copy构造函数或copy assignment操作符声明为private。籍由明确声明一个成员函数,你阻止了编译器暗自创建其专属版本;而令这些函数为private,使你得以成功阻止人们调用它。
一般而言这个做法是绝对不安全的,因为member函数和friend函数还是可以调用你的private函数。除非你够聪明,不去定义它们,那么如果某些人不慎调用任何一个,会获得一个链接错误(linkage error)“将成员函数声明为private而且故意不实现它们”这一伎俩如此为大家接受,因此被用在c++iostream程序中阻止copying行为。是的,看看你手上的标准程序库实现码中的ios_base,basic_ios和sentry。你会发现无论哪一个,其copy构造函数和copy assignment操作符都被声明为private而且没有定义。
注意:为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。使用像uncopyable这样的base class也是一种做法。
注意:为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。就像用uncopyable这样的base class也是一种做法

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

有许多做法可以记录时间,因此设计一个TimeKeeper base class 和一些derived classes 作为不同的计时方法,相当合情合理:
class TimeKeeper{
public:
TimeKeeper();
~TimeKeeper();

};
class AtomicClock:public TimeKeeper();//原子钟
class WhaterClock:public TimeKeeper();//水钟
class WristClock:public TimeKeeper();//腕表
许多客户想在程序中使用时间,不想操心如何计算等细节,这时候我们可以设计factory函数,返回指针指向一个计时对象。Factory函数会“返回一个base class指针,指向新生成之derived class对象”:
TimeKeeper*ptr getTimeKeeper();//返回一个指针,指向一个
//TimeKeeper派生类的动态分配对象
注意:

  1. polymorphic base classes应该声明一个virtual析构函数。如果class带有任何cirtual函数,它就应该拥有一个virtual析构函数。
  2. Class的设计目的如果不是作为base class使用,或不是为了具备多态性,就不该声明virtual析构函数。

08别让异常逃离析构函数

C++并不禁止函数吐出异常,但它不鼓励你这样做,这是有道理的,考虑一下代码:class Widget{
public
~widget(){}//假设这个可能吐出一个异常
};
void doSomething()
{
std:::vector v;
//这里被自动销毁
}
当vector被销毁,它有责任销毁其内含所以widgets。假设v内含十个widget,而在析构第一个元素期间,有个异常被抛出,其他九个widgets还是应该被销毁,因此v应该调用他们各个析构函数。但假设在那调用期间,第二个widget析构又抛出异常。现在有两个同时作用的异常,这对C++而言太多了。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。使用标准程序库的其他容器(如list set)或TR1任何容器甚至array,也会出现相同情况。容器或array并非遇到麻烦的必要条件,只要析构函数吐出异常,即使并非使用容器或arrays,程序也可能过早或出现不明确行为。是的,C++不喜欢析构函数吐出的异常!
注意:

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

总结

孔子云:“取乎其上,得乎其中;取乎其中,得乎其下;取乎其下,则无所得矣”。对于读书而言,这句古训教训我们去读好书,最好是好书中的上品-经典书。科技人员要读的技术书,因为直接关乎客观是非与生产效率,阅读选材本更应慎重。但是经典书一下吃透很难,需要花费大量的时间来阅读才能掌握其中的知识,但是阅读是一个好习惯,阅读更能提升一个人的品行修养和知识储备,同时阅读又是一种感悟和享受,是一种愉悦。我阅读此书把它写成博客的目的也是为了养成这种阅读的好习惯并和大家分享学习的快乐!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神之子-小佳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值