Effective C++构造函数析构函数Assignment运算符
在看《Effective C++》这本书的过程中,我无数次的发出感叹,这他妈写得太好了,句句一针见血,直接说到点上。所以决定把这本书的内容加上自己的理解写成5篇博客,我觉得不管你是否理解这些条款,都值得你先记下来。下面的索引对应的是书中的章节。
11:如果class内动态配置有内存,请为此class声明一个copy constructor和一个assignment运算符
12:在constructor中尽量initialization动作取代assignment工作
13:initialization list中的members初始化次序应该和其在class内的声明次序相同
14:总上base class拥有virtual destructor
15:令operator =传回*this的reference
16:在operator=中为所有data member赋值
17:在operator =中检查是否自己赋值给自己
11:如果class内动态配置有内存,请为此class声明一个copy constructor和一个assignment运算符。
默认的copy constructor和operator=不会对类的每个data member一一赋值,而是一个简单引用,让左边的对象指向右边对象所指的对象。再也没有起动作,如果这个类动态分配了内存,比如左边对象本来指向一块内存A,现在左边的对象指向右边对象所指的内存B了,而再也没有其他对象指向内存A,由于它是动态分配,不会自己回收,所以就出现内存泄露,还有就是两个对象指向同一块内存,如果其中一个对象出了其作用域,那么其析构函数将自动调用,其动态分配的内存将被回收,现在另一个对象却指向一块已经被回收的内存,只要一调用这个对象的数据,就会出现不可知的异常,还有一点值得一提,就算一个对象没有指向任何地址或是它所指的地址已经被回收了,在调用它的方法的时候,只要它的方法没有使用它的data member(肯定不存在),就不会出现任何问题,因为方法的内存是和对象类型一起分配,实例化一个对象的时候不会为方法分配内存,只会为data member及其他一些指针分配内存,如指向父类的指针,指向虚拟表的指针等。
如11所述,如果不声明那两个方法,在方法调用的时候也会出现问题,当这个对象是以传值的方式被调用时,会产生一个临时变量,这个临时变量会引用这个对象,当方法执行完成,这个临时变量超出它的作用域,析构函数被调用,这个对象就这样被销毁了。所以你必须遵守这一条规则。
12:在constructor中尽量initialization动作取代assignment工作
对象的构造分两个阶段:
1:data member被初始化
2:被调用的构造函数执行起来
如果在构造函数中对data member一一赋值,那么先要调用data member的构造函数,如果你没有为data member赋初值,那么调用的是默认的构造函数,如果你赋了初值调用的是copy constructor,但是我的编译器不允许data member在定义的时候赋初值,那么就是调用默认的构造函数,当你在构造函数内为data member赋值的时候调用operator =,相当于你调用了一次constructor和一次operator =,而initialization 只调用一次copy constructor,因为在data member初始化的时候已经为data member赋值了,在构造函数里面就不用为data member赋值了,经常会遇到这样的面试题:一个data member在定义的时候给他一个初始值,又在构造函数内赋另一个值,请问这个data member现在的值是多少?还有一些就是base class中的一个data member,多处赋值,然后问题最后它的值是多少?只要记住父类的构造函数在子类的构造函数之前执行,初始化参数在构造函数之前执行。
总之一句话initialization效率比在构造函数中赋值的效率高,如果data member很多且需要初始化成同一个值,而且效率不是那么重要的话,可以在构造函数中用连等式赋值,这样会清晰明了一点。效率不是永远都放在第一位的,代码的可读性也很重要。82法则还记得吗,我曾经因为多写了一个if,在代码评审中被批评,理由就是100万访问的时候会影响效率,在两家公司遇到过这种情况,什么都是这个理由,百万级访问时会影响效率。
13:initialization list中的members初始化次序应该和其在class内的声明次序相同
有时候data member的初始化是依赖别的data member的,那么data member的初始化顺序就必须弄清楚。data member的初始化顺序与其在在initialization中出现的顺序无关,只与它们定义的顺序有关,先定义的data member会在initialization中先初始化,在destructor中后析构,先初始化的data member后析构,所以base data member后析构,跟栈中的变量一样先定义的变量后析构一样。如果类继承多个类,那么base data member的初始化顺序由继承的先后顺序决定,先继承的先初始化。
14:总上base class拥有virtual constructor
在继承关系中,是调用父类的方法还是调用子类的方法,这个动态的实现是由虚拟函数来决定的,含有虚拟函数的类都有一个虚拟函数表,如果父类中的方法是virtual的,如果没有子类没有覆写这个方法那么就是直接继承过来,不管子类还是父类调用这个方法产生的结果是一样的,如果子类覆写了这个方法,那么子类的虚拟表中存的就是子类方法的地址,如果一个父类的指针指向子类,如果方法被子类覆写了,那么调用的方法就是子类的方法,如果没有覆写那么就是调用父类的方法。如果父类的方法不是virtual的,而且子类有一个一样的方法,那么父类的方法不会被子类覆写,也就是说:父类指向子类的指针调用的将不会是子类的方法,而是父类的方法。同样的父类的析构函数不是virtual的,那么在delete 这个指针的时候就会出现不可知的情况,反正之类的构造函数是不会调用的,所以当你决定让一个类成为父类,那么就让他的destructor为virtual。
但是也不需要让每一个类的destructor成为virtual的,因为含有virtual方法的类都有一个指向virtual table的指针,会让对象变大,如果对象本来就不太的话可能会出现成本翻倍的情况。只有当class中含有至少一个虚拟方法时才让他的析构函数成为虚拟的。
15:令operator =传回*this的reference
先看一个等式:(A=B)=C,为了实现这种连等式,operator=肯定是不能返回void的,你可以为*void赋值,但是你不能为void赋值,operator=的返回方式不能是by value,如果是这样的话,(A=B)返回的是A或是B的副本(正确的方式应该是A的reference),让后将C的值赋给这个副本,而A、B的值却没有发生任何变化,这当然不是我们想要的,为了不让这个副本的产生,返回值必须是引用的方式,你可以用指针或是reference的方式返回,指针必须加个*麻烦,所以就是以reference的方式返回,那么是返回A的引用还是B的引用,毫无疑问是A的,如果是B的,那么执行的顺序是这样的,先将B赋给A,然后将C的值赋给B,赋值其实就是将右边的值赋给左边的返回值吗!这样然不是我们想要的,我想要的其实是将B赋给A,然后将C在赋给A,当然写这样等式的绝对不是一个合格的程序员。所以operator=必须返回左边对象的reference。我一直在想this为什么是一个指针类型而不是一个reference呢?因为我们必须在operator=方法的最后一句加上return *this;而不是 return this;
16:在operator=中为所有data member赋值
这是毫无疑问的,如果有部分data member没有被赋值,那么被赋值的对象不就是残废了的吗!有时候我们可能会在为类加data member的时候忘了再operator=中为它赋值了,或是在子类的operator=中忘了为父类的data member赋值。当然这些都不是重点,不记得不是问题,出错了自然就知道了,为父类的data member赋值只需要在子类的operator=中加上Base::operator=(this);如果编译器不支持调用base的operator=的话,可以做类型转换啊,static_case<&Base>(*this)=Derived;我发现在类型转换中,转换成的类型一般都是指针或是引用,特别是转换类型在左边的时候就一定不能是by value的方式,会产生临时变量,然后给临时变量赋值,当然不是你想要的。在为指针类型的data member 赋值时要记住一点,是为指针所指的对象赋值,而不是为指针赋值,如果你让data member一会儿指向这一会指向那的,可能会出现某些地址不可到达而出现内存泄露。
17:在operator =中检查是否自己赋值给自己
在为一个对象赋值之前,先要确认这个对象是否动态配置内存,如果动态配置内存,先要回收掉这块内存,不然当这个对象被赋值,指向别的地址后就会出现内存泄露,当然也不能一发现它动态配置内存了就把它先回收掉,因为可能出现把自己赋给自己的情况,你总不能因为把自己赋给自己之后,自己就莫名其妙的被回收了吧,所以在operator=中药检查是否自己赋值给自己。
Effective C++系列:
Effective C++构造函数析构函数Assignment运算符
作者:陈太汉
博客:http://www.cnblogs.com/hlxs/
本文转自啊汉博客园博客,原文链接:http://www.cnblogs.com/hlxs/archive/2012/07/15/2592796.html

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
java类的访问权限
1.解析 Java有四种访问权限, 其中三种有访问权限修饰符,分别为private,public和protected,还有一种不带任何修饰符。 private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。 default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。 protect: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。 public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。 下面用表格的形式来展示四种访问权限之间的异同点,这样会更加形象。表格如下所示: 同一个类 同一个包 不同包的子类 不同包的非子类 Private √ Default √ √ Protected √ √ √ Public √...
- 下一篇
读取excel并将其转换为xml
1.前言 项目开发过程中需要读取excel文档,并将excel文档中的内容转化为xml文档并保存在本地文件中。 比如要读取如下格式的excel文档: 通过代码实例中如下的代码段可以运行excel中有空行: if (cell == null) { item.setText(""); e.addContent(item); cellNum++;//如果存在空列,那么cellNum增加1,这一步很重要。 continue; } 2.代码示例。 package edu.sjtu.erplab.jdom;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.text.SimpleDateFormat;import java.util.Date;import org.apache.po...
相关文章
文章评论
共有0条评论来说两句吧...