《EffectiveC++》读书笔记(二)条款4-6

来源:互联网 发布:祛痘消炎的药膏知乎 编辑:程序博客网 时间:2024/05/08 07:47

正文

Item 4: Make sure that objects are initialized before they’re used

想要完成这个Item很简单,就是落在我们的构造函数上,因为它的作用便是掌控类的初始化,这里的关键是将每一个成员变量都初始化

分清什么是初始化,什么是赋值

首先需要指出的是,初始化(initialize)和赋值(assign)并不一样。这让我想起了本学期开始学的Java。
在Java课上和Java书上都是这样写构造方法的:

public class A{    public A(int b){        this.a = b;    }    private int a;}

但是在写C++时确很少这样,我们往往都是这样写

//Plan Aclass A{public:    A(int a, string b):a_(a),b_(b){}private:    int a_;    string b_;};

当然C++也可以这样写

//Plan Bclass A{public:    A(int a, string b){        this->a = a;         this->c = b;    }    int a;    string c;};

那么问题来啦,在C++中这两种构造函数的写法有区别吗??

答案是有的,在Plan A中,我们通过成员初始化列(member initialization list)来进行初始化,而Plan B则是进行了赋值(assign)。

哪种更好?为什么?

C++规定,对象的成员变量的初始化发生在进入对象的构造函数体之前

在Plan B 中, 成员变量在被赋值之前已经完成了初始化,如果这个变量是一个对象(如这里的string),那么它先调用它的default构造函数进行初始化,然后进入类A的构造函数体,再进行赋值,可以看到,前一步调用的default构造函数完全被浪费了。而在Plan A中,则直接调用对象的copy构造函数进行初始化。

大多数情况下,调用default构造函数+copy赋值运算符比只调用copy构造函数效率要低,所以我们更提倡Plan A的写法。

当然,如果是一个内置类型(如int)就不存在效率的问题,但为了一致性,还是把它也这样写吧。
同样,如果你需要一个成员对象在构造函数中只default构造,那么不加参数就可以了。
并且对于一个const成员或者reference,它们一定要初值,而不能被赋值。
无论如何,为了避免出错,在初始化列中列出所有成员变量

由于C++成员初始化顺序固定,基类早于派生类被初始化,类中的成员变量按照其声明顺序进行初始化。所以我们在初始化列中的成员变量顺序并不影响其初始化顺序,我们应该在成员初始化列中也按照其声明顺序进行初始化

关于初始化顺序的补充

首先给出结论:不同编译单元内的non-local static 对象的初始化顺序不确定

编译单元指的是可以编译为.o文件的代码。
static对象就是全局对象,在namespace作用域的对象;类内,函数内,文件作用域内的static对象。
non-local对象就是不在函数内定义的对象。

而由于这样的对象初始化顺序不确定,当我们两个这样的对象初始化具有依赖关系时,可以通过用一个返回static对象的引用的函数来解决

Object &getMyObject() // 是不是想到了单例模式。。。{    static Object myObject;    return myObject;}

这样就保证需要myObject时它一定被初始化了,不用它就不会初始化。

Item 5: Know what functions C++ silently writes and calls.

当我们编写自己的类时,编译器会为我们声明一些public inline函数,包括default 构造函数,copy构造函数,copy assignment操作符,和析构函数。
注意这仅仅是声明,只有当它们被调用时,编译器才会把它们实现。

编译器定义的构造函数和析构函数会依次执行成员变量的构造函数(这里的构造函数是无参的,)和析构函数。copy构造函数,copy assignment操作符则会将non-static成员一个个的copy过去。

但编译器定义的函数必须保证其生成的代码合法且有意义

如果一个成员对象的构造函数是个private,或者没有无参的构造函数,那么自然不合法。
如果一个成员对象是一个派生类,而其基类的copy构造函数为private,那么由于派生类会先处理基类成员(调用基类的copy构造函数),自然也不合法。
如果一个成员变量为对象的引用,那么修改这个引用则改变其对象,万一有其他的引用也引用了这个对象呢?如果一个成员变量是const呢,该如何赋值,这样的代码便没有意义。。。

所以遇到这样的情况编译器会拒绝为其生成函数,而如果你的代码中又需要这样的操作。。。那就会报错的。。。

Item 6 :Explicitly disallow the use of compiler-generated functions you do not want.

对于某些类来说,其不应该被copy或赋值。

比如,让我们来编写魔兽争霸3的代码,那么剑圣(英雄)和大G(兽族步兵)便是两种不同的对象,对于两个步兵来说,它们其实除了血量来说没有什么不同,而剑圣只有一个,连名字都是独一无二的。

又或者说,做手机的有很多,但Jobs只有一个。

陈硕的文章里提到了值语义和对象语义,我认为就是这样的,对于值来说它是可以被copy或赋值的,而对于一个对象来说,它是独一无二的,那么它不应该被copy或赋值。

既然不应该,那么我就需要明确的指出,让编译器帮助我们进行限制(类似的思想有很多,如 const,overwide或者Java中的@overwide,都是编译器在帮助我们)。

书中主要是用了将不需要的函数声明为private ,(什么。你说不需要不声明就好了,请看上一条item),或者编写一个nocopyable的类,在类里将不需要的函数声明为private来继承(原因依然参照上一条item)。

毕竟本书写于2005年,而陈硕的muduo中也是采用继承boost::noncopyable,在C++11中我们直接将其声明为=delete就行了。

原创粉丝点击