<<Effective C++>> 读书笔记

条款04:确定对象被使用前已先被初始化

int x;

在某些语境下,x被保证被初始化为0,但某些语境却不保证,现在,我们终于有了一些规则

1.对于无任何成员的内置类型,必须手工完成初始化

int x = 0;
const char* text = "A C-style string";

double d;
std::cin >> d;

2.对于内置类型以外的任何其他东西,初始化落在构造函数身上,规则是:确保每一个构造函数都将对象的每一个成员初始化

对于初始化,很多人都把赋值与之混淆,观察以下代码

class student {
    public:
        student(string name,string address,int phones);
    private:
        string StuName;
        string StuAddress;
        int StuPhones;
};
student :: stduent(string name,string address,int phones)  {
    StuName = name;             // 这些都是赋值而非初始化
    StuAddress = address;
    StuPhones = phones;
}

Student :: student(string name,string address,int phones)
:StuName(name),StuAddress(address),StuPhones(phones) {} // 这些都是初始化

第二版的构造函数(用初始化列表)通常比第一个构造函数(赋值操作)效率更高,由于赋值那个版本会先调用默认构造函数(string类的和int类的)为StuName,StuAddress,StuPhones设初值,然后再对它们赋值(string类的copy赋值和int类的copy赋值).使用初始化列表的那个版本避免了这个问题,因为初始化列表里的实参,会为各个成员变量进行拷贝构造(string类的和int类的),例如,本例中的StuName以name为初值进行copy构造,StuAddress以address为初值进行copy构造,StuPhones以phones为初值进行copy构造.

相比调用默认构造然后调用拷贝赋值,只调用一次copy构造函数的效率是比较高的.

如果面对成员变量属于内置类型,(初始化与赋值的成本相同),也一定要设初始化列表来初始化(有些情况还是可以不用的),如果成员变量是const或references,它们就必须要初始化列表来初始化,因为const类型无法被赋值,而references类型一定要有初值.

如果class内有很多个构造函数,根据规则2,每个构造函数又必须对每个成员初始化,这样会导致很多的重复.这种情况下可以灵活的在初始化列表遗漏那些"赋值表现得像初始化一样好"的成员变量,例如一些int,double,bool等内置类型,改用它们的赋值操作,然后将这些赋值操作放在某个函数(一般是private,因为不需要外部调用而且经常在构造函数里调用)供所有的构造函数调用.

C++对于"成员初始化次序"有着十分固定的规则:base classes总是更早与derived class初始化(想想也是,没有父类哪来的子类,值得一提的是,析构函数则是从子类开始),class的成员变量则总是以其声明的次序来被初始化.

注:此条款后续内容还会更新(作者太菜,看不是很懂,等境界到了再回头补充)

谨记:

1.为内置型对象进行手工初始化,因为C++不保证初始化它们

2.构造函数最好使用初始化列表,而不要在构造函数本体内使用赋值操作.初始化列表列出的成员变量,其排列次序应该和它们在class的声明中的次序一致

3.为免除"跨编译单元之初始化次序"问题,请以local static对象替换non-local static对象

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

我们知道,在类里没有声明构造函数,析构函数,拷贝构造函数,拷贝赋值函数时,编译器就会为该类声明一个编译器版本的,并且这些函数都是public且inline的,就像以下代码

class Empty {}; // 当你写出这行代码

class Empty { // 好像你写下这样的代码
    public :
        Empty()  {...}
        Empty(const Empty& rhs)  {...}
        ~Empty()  {...}
        Empty& operator = (const Empty& rhs)  {...}
};

值得注意的是,如果class没有三大函数时(析构函数,copy构造函数,copy赋值函数),编译器就会声明它们,而当调用了它们之后,编译器才会生成它们.而且,编译器声明的析构函数是non-virtual函数,除非这个class的base class有virtual析构函数.

至于copy构造函数和copy赋值函数,编译器创建的版本只是单纯地将来源对象的每一个non-static成员变量拷贝到目标对象,记得在课堂上,老师也强调过这两个默认函数,执行的只是浅拷贝,需要注意类内有没有指针类型,如果有指针的话,就必须自己写这两个函数(当然了,析构函数也要写),目的是为了防止悬空指针的出现(写析构函数是为了防止内存泄漏).

对于copy构造函数,编译器只对每一个bits进行拷贝,比较简单.如果你未提供,编译器总会给你一个默认的版本.

而对于copy赋值函数,编译器有时候压根就不给你默认定义copy赋值函数,我们来看以下代码

template<typename T>
class NameObject  {
    public:
        NameObject(string& name,const T& value) : nameValue(name),objectValue(value) {}
        // 还记得条款3吗,必须用初始化列表来初始化const和引用哦
    private:
        string& nameValue;  // 是一个reference
        const T objectValue; // 是一个const
};

在主函数测试以下代码

    string str1 = "newdog";
    string str2 = "olddog";
    NameObject<int> p(str1,2);
    NameObject<int> s(str2,36);

    p = s;

 编译器对p = s报错,原因是编译器没有为我们生成拷贝赋值函数

事实上,当你打算在一个"类内含reference成员或者const成员"的class内支持赋值操作,就必须自己定义一个copy赋值函数(写这个的时候注意对象的自我赋值哦),编译器是不会为你生成的,原因很简单,我们都知道,引用就是一个变量的别名,一旦指定一个变量后,便不能再指定别的了(指针则可以,形象来说,指针比较花心,引用比较专情hh),同样的,const类型也不能更改,那就拿p = s来说,p内含有一个引用或者const成员,C++都不允许改变它.所以编译器无法为你提供copy赋值函数.

另外还有最后一种情况,如果base classes将copy assignment操作符声明为private,编译器将拒绝为其derived classes生成一个copy assignment操作符,这一点会在条款06和条款12再提到.

谨记:

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

2.编译器不会提供"类内含reference成员或者const成员"的class拷贝赋值操作符

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值