IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> EffectiveC++-条款04:确定对象使用前已先被初始化 -> 正文阅读

[C++知识库]EffectiveC++-条款04:确定对象使用前已先被初始化

确定对象使用前已先被初始化

1. 初始化的烦恼

读取未初始化的变量,会导致不明确的行为。在某些情况下,仅仅是读取未初始化的值,就会让你的程序崩溃。也有可能,读入一些“半随机”的bits,导致你的程序执行不可预测的路线。最终使得自己陷入不愉快的Debug过程。

2. 保证初始化

先谈谈最佳的解决方法:永远在使用对象之前先将它初始化。对于无任何成员的内置类型,你得手动的完成这件事。
以下是例子

int x=100;
const char* y="HelloWorld";
double z;
std::cin>>z;

至于内置类型之外的数据类型,初始化的责任落在构造函数身上。
规则很简单:确保每一个构造函数都将对象的每一个成员初始化。

class AInit:public AActor
{
public:
    int TestInt=100;
    double TestDouble=100.0;
public:
    AInit():
    TestInt(1),
    TestDouble(99.99){
        TestInt=1;
        TestDouble=99.9;
    }
}
-

我们可以看到有三种赋初值的方式,它们执行的顺序是先定义(初始化),再进入构造函数时的成员初始化列表(初始化),再构造函数内的语句(此时是赋值)。=-=
其中,比较推荐的是在初始化列表里对成员变量进行初始化,且规定总是在初值列列出所有成员变量,避免遗漏造成不明确的行为。
对于未在初始列指定初值的成员,编译器会自动调用default构造函数(也就是说会先调用默认构造函数,在执行构造函数内的语句)。
对于内置类型,有时候也需要使用初值列,如果成员变量是const或者references,他们就一定需要初值。
为了避免记住那些成员需要初始化,最简单的做法就是:总是使用初值列。
在Classes中,可能具有很多构造函数,每个函数都有自己的初值列。当成员过多的时候,会导致不受欢迎的重复和无聊的工作。
这种情况下可以合理的省略那些“赋值表现的像初始化一样好”的成员变量,改用它们的赋值操作。当需要从文件或者数据库读入时这种方式很有用。当然,使用成员初值列完成真正的初始化通常更加可取。

3. 初始化的次序

C++有着十分固定的“成员初始化次序“。
基类比其派生类更早初始化。
类的成员变量总是以其声明的次序被初始化。(与在成员初值列的次序无关,为了不引起疑惑,在初值列的次序应该以声明次序作为参考)
如果变量初始化具有依赖关系的话,那么在代码中的次序需要被调整。

4. 不同编译单元间的non-local static对象的初始化

我们一步一步理解。
所谓static对象,其生命周期从被构造出来到程序结束为止(作者的意思不只是用static关键字修饰的对象,广义的static?,这点容易不理解)。所以不包括stack对象和heap-based对象。主要指global对象,定义于namespace的对象,在classes内,在函数内,以及在file作用域被声明static的对象。函数内的static对象被称为local static对象(因为它们相对函数而言是local),其他static对象被称为non-static对象。
程序结束时static对象会自动销毁,也就是它们的析构函数会在main函数结束时被调用。

所谓编译单元(translation unit)是指产出单一目标文件(single object file)的那些源码,基本上就是单一的源码文件加上其所含的头文件。

然后现在这里有一个问题,如果某编译单元某个non-local static对象的初始化使用了另一个编译单元的non-local static对象,但是用到的这个对象可能尚未被初始化。因为C++对于定义在”不同编译单元内的non-local static对象”的初始化次序并无规定。(因为决定它们的初始化次序相当困难,甚至无解

A.h

class ClassA{
public:
	int number=100;
};
ClassA a;//这行放到cpp文件

B.h

#include "A.h"
extern ClassA a;
class ClassB{
public:
	ClassB{
		std::cout<<a.number;
	}
};

然后我们怎么去解决呢?
幸运的是,一个小小的设计就可以了,我们唯一要做的是:将每一个non-local static对象搬到自己类的专属函数中去。(该对象在此函数中被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接涉及这些对象。换句话说,non-local static对象被local static对象替代了。
对于那些熟悉设计模式的伙伴们,一定认出来这就是大名鼎鼎的单例模式(Singleton)一个常见的实现手法。
这个手法的基础在于:C++保证,函数内的local static对象会在该函数首次调用被初始化。

class ASingleton:public AActor
{
public:
    int TestInt=100;
public:
   static ASingleton& StaticClass(){
        static ASingleton Singleton;
        return Singleton;
    }
};

但是注意,在多线程的情况下,可能会出现一些初始化竞争问题。
处理这个麻烦的一种做法:在程序的单线程启动阶段手工调用所有reference-returning函数。

当然,运用reference-returning函数可防止“初始化次序问题”,前提是对象有一个合理的初始化次序。如果对象A必须在对象B之前初始化,但A的初始化是否成功却又受制于B是否已初始化,这时候你就有麻烦了。如果避开如此病态的境况,此处描述的办法应该可以提供良好的服务,至少在单线程的程序中。

总结

  • 为内置型对象进行手工初始化,因为C++不保证初始化它们。
  • 构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 20:30:29  更:2022-03-21 20:32:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:32:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码