Effective C++学习笔记——确定对象被使用前已先被初始化

目录

一.为什么要初始化

二.初始化与赋值,善用初始化列表

三.不同编译单元内定义的非局部静态对象的初始化顺序


一.为什么要初始化

初始化可以让我们避免很多不可预知的问题。

比如下列伪代码:

class Human
{
    ...
    public:
        int* arr;
        ...
}

在这里我们并没有对arr指针进行初始化。如果我的编译器不会自行初始化,那么虽然我自己知道arr并没有初始化,但是如果其他人调用了我这个arr指针又恰巧不知道这一点,那就糟糕了。

比如他可能写出下列代码:

while(arr != nullptr)
{
    std::cout<< *arr << endl;
    ++arr;
}

当我们在vs2019上运行时就会报错:

 这其实还好,但假如编译器比较笨并没有进行相关语法检查那就会出大问题。

我们不能妄想每个人都能记得创建的对象是否初始化了,因此对于所创建的每个对象都初始化就是非常高效且有益的事。

正如原文所说:永远在使用对象之前先将它初始化

二.初始化与赋值,善用初始化列表

首先来看一个代码:

class Human
{
    public:
        Human()
        {
            name = "me";//赋值
            age = 20;
        }
    private:
        string name;
        int age;
}

当我们创建Human对象调用构造函数时,会先对name和age初始化,之后才将"me"、20赋予这两个成员。

对于name成员而言需要先调用string的构造函数,然后再调用string的赋值重载函数,这太冗余了!假如我们在name初始化时就赋予了它应得的值,那么就只需要调用string的构造函数即可。

class Human
{
    public:
        Human()
            :name("me")//使用初始化列表对成员初始化
            ,age(20)
        { }        
    private:
        string name;
        int age;
}

 虽然结果是一样的,但是这样合二为一,效率更高。

因此我们应该在构造函数还未调用——还未初始化成员时,调用初始化列表对成员进行初始化。

另外,使用初始化列表时最好全部成员都进行初始化。防止因为遗忘而出现纰漏。

同时,我们也需要注意初始化列表是按照成员声明顺序进行初始化顺序

//这样的代码并不能为arr正确分配空间
class Human
{
    public:
        Human()
            :size(16)
            ,arr(new int[size])//编译器会先给arr初始化
        { }        
    private:
        int* arr;//按照声明顺序会先初始化arr,后size
        int size;
        ...        
}

//这是正确的次序
class Human
{
    public:
        Human()
            :size(16)
            ,arr(new int[size])
            /* :arr(new int[size])
               ,size(16)
                这样也是可以的
            */                    
        { }        
    private:
        int size;//按照声明顺序会先初始化size,后arr
        int* arr
        ...        
}

三.不同编译单元内定义的非局部静态对象的初始化顺序

不同编译单元(源文件)内的对象如果其中一方要使用另一方,那么一定要注意初始化的次序,倘如被借用的一方尚未初始化,那么会发生意想不到的错误。 

小编将《Effective C++》中代码示例做了调整,以便讲解展示:

下面有两个类Par、Child分别位于不同的编译单元内。

我们所期待的结果是当Child对象B定义时,调用自己构造函数打印Par对象A中的Hello world。

//头文件<Par.h>
#include<iostream>
#include<string>
using namespace std;

class Par
{
public:
	Par()
		:str("Hello world")
	{}
	string str;
};

extern Par A;
//源文件<Par.cpp>
#include"Par.h"
Par A;
//源文件<Child.cpp>
#include"Par.h"

class Child
{
public:
	Child()
	{
		cout << A.str << endl;
	}
};

Child B;
//源文件<Main.cpp>
#include"Par.h"

int main()
{
	
	return 0;
}

这个程序中我们无法确定child对象B的初始化结果

当Child.cpp先编译时,是段错误

 当Par.cpp先编译时,打印Hello world,是预期结果

这就是因为Par、Child两个类在不同的编译单元内,造成如果Child先编译了那么Par对象A就没有初始化,从而产生错误。

解决方法自然是有的:

C++中规定,函数中的局部static对象会在函数使用时,首次遇上其定义式时初始化。意思就是说当使用函数时,函数中的static对象一定是已经初始化过的了。

因此,我们可以将对象A声明为static封装进一个函数中,函数返回A的引用。

在对象B的构造函数中调用这个函数,又因为上述特性,函数返回的引用A一定是经过初始化的。

//头文件<Par.h>
#include<iostream>
#include<string>
using namespace std;

class Par
{
public:
	Par()
		:str("Hello world")
	{}
	string str;
};

Par& GetPar();//函数声明
//源文件<Par.cpp>
#include"Par.h"
Par& GetPar()//函数定义
{
    static Par A;
    return A;
}

/*
    当调用GetPar函数时,
    A对象是已经初始化的。
*/
//源文件<Child.cpp>
#include"Par.h"

class Child
{
public:
	Child()
	{
        //构造时直接调用函数间接获取Par对象。
		cout << GetPar().str << endl;
	}
};

Child B;

 Main.cpp不变。

现在即便Child.cpp先编译也无所谓。

当然,要尽量避免不同编译单元的类出现相互调用的情况,这会使问题变得麻烦且复杂。 

 

 

 

 

 一个好的程序员应该是那种过单行线都要往两边看的人。— Doug Linder


如有错误,敬请斧正 

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

就要 宅在家

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

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

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

打赏作者

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

抵扣说明:

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

余额充值