当前位置: 首页 > news >正文

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


如有错误,敬请斧正 

相关文章:

  • 一文弄懂 HashMap 中的位运算
  • 【易购管理系统】路由界面基础搭建
  • Linux系统常规异常报错解决汇总:
  • 【编程语言】什么是闭包?你可能经常在用它,但不知道它叫闭包!
  • 【live2D看板娘】为你的网站添加萌萌的二次元板娘,这都拿不下你?
  • 信息学奥赛一本通:1014:与圆相关的计算
  • 【APP 逆向百例】Frida 初体验,root 检测与加密字符串定位
  • 安卓中listview中性能优化的处理
  • 期刊论文-写作-投稿-工具等的经验合集
  • Java 属性文件乱码问题
  • 腾讯毕业复盘
  • C语言基础篇
  • 虚拟机搭建Redis 远程密码可访问,并且后台运行
  • 4
  • Visual Studio Code讲解(一) 安装及常规配置
  • ES6 ...操作符
  • JavaScript服务器推送技术之 WebSocket
  • Kibana配置logstash,报表一体化
  • npx命令介绍
  • Promise初体验
  • Redux系列x:源码分析
  • 闭包--闭包作用之保存(一)
  • 利用jquery编写加法运算验证码
  • 模型微调
  • 驱动程序原理
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 试着探索高并发下的系统架构面貌
  • 数据科学 第 3 章 11 字符串处理
  • 用mpvue开发微信小程序
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • ​VRRP 虚拟路由冗余协议(华为)
  • #DBA杂记1
  • #每天一道面试题# 什么是MySQL的回表查询
  • (6)设计一个TimeMap
  • (C语言)二分查找 超详细
  • (day 12)JavaScript学习笔记(数组3)
  • (poj1.3.2)1791(构造法模拟)
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (四)Linux Shell编程——输入输出重定向
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • ***检测工具之RKHunter AIDE
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .Net 中Partitioner static与dynamic的性能对比
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .NET命名规范和开发约定
  • .NET文档生成工具ADB使用图文教程
  • .Net转Java自学之路—SpringMVC框架篇六(异常处理)
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • @Documented注解的作用
  • @Transactional 详解