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

张开发
2026/4/21 17:14:54 15 分钟阅读
条款04:确定对象被使用前已先被初始化
C并不能保证每个对象在定义时都被自动初始化。就像书中第一条提到的一样C包含多种子语言例如定义一个C风格的整型数组(int[])时其中就可能包含非零初始化的元素而在定义标准库(STL)中的容器时例如一个整型向量(std::vector)其强大的函数库可以保证所有元素都被零初始化。自有类型(built-in type)的初始化C的自有类型继承于C因此不能保证此类型的变量在定义时被初始化。使用未初始化的数据可能会导致程序不正常运作因此在定义变量的时候需要对其进行初始化例如将如下代码:intx;doubled;改为intx0;doubled;std::cind;类的初始化对于用户自定义的类我们需要构造函数(constructor)来完成此类的初始化例如:classCoordinate{private:intx;doubley;conststd::listdoublenum;public:Coordinate(constint_x,constint_y,conststd::listdouble_num);};//以下构造函数为成员x, y, num赋值来完成对象的初始化Coordinate::Coordinate(constint_x,constint_y,conststd::listdouble_num){x_x;y_y;num_num;}可能这是一个易于记忆的方法但并不是最好的方法因为此构造函数并没有真正完成“初始化”只不过是做了“赋值”的操作。而C规定在进入构造函数之前如果用户没有规定初始化过程C将自动调用各成员对应类型的默认构造函数。这样一来此构造函数就相当于先调用了C的默认构造函数又做了一次赋值操作覆盖掉了先前的结果造成了浪费。解决方法使用初始化列表(initialization list)C就不必额外调用默认构造函数了Coordinate::Coordinate(constint_x,constint_y,conststd::listdouble_num):x(_x),y(_y),num(_num){}另外**构造函数是可以被重载(overload)的**对于这个我们自己定义的类还需要一个没有参数输入的默认构造函数因此我们可以定义:Coordinate::Coordinate():x(0),y(0),num(){}//num()调用了std::listdouble类型的默认构造函数某些初始化是语法必要的例如在定义引用(reference)和常量(const)时不将其初始化会导致编译器报错constinta;//报错需要初始化intb;//报错需要初始化//现在对其进行初始化constinta3;//编译通过intc3;intbc;//编译通过数据初始化的顺序在继承关系中基类(base class)总是先被初始化。在同一类中成员数据的初始化顺序与其声明顺序是一致的而不是初始化列表的顺序。因此为了代码一致性要保证初始化列表的顺序与成员数据声明的顺序是一样的。classmyClass{private:inta;intb;intc;public:myClass(int_a,int_b,int_c);};//注意即使初始化列表是c-b-a的顺序真正的初始化顺序还是按照a-b-cmyClass::myClass(int_a,int_b,int_c):c(_c),a(_a),b(_b){}初始化非本地静态对象现在还有一种特殊情况尤其是在大型项目中比较普遍在两个编译单元中分别包含至少一个非本地静态对象当这些对象发生互动时它们的初始化顺序是不确定的所以直接使用这些变量就会给程序的运行带来风险。先简要解释一下概念编译单元(translation unit): 可以让编译器生成代码的基本单元一般一个源代码文件就是一个编译单元。非本地静态对象(non-local static object): 静态对象可以是在全局范围定义的变量在名空间范围定义的变量函数范围内定义为static的变量类的范围内定义为static的变量而除了函数中的静态对象是本地的其他都是非本地的。非局部静态变量就是非本地静态变量例如全局对象全局静态变量等此外注意静态对象存在于程序的开始到结束所以它不是基于堆(heap)或者栈(stack)的。初始化的静态对象存在于.data中未初始化的则存在于.bss中。回到问题现有以下服务器代码:classServer{...};externServer server;//定义在全局范围声明外部对象server供外部使用又有某客户端classClient{...};Client::Client(...){numberserver.number;}Client client;//在全局范围定义client对象自动调用了Client类的构造函数以上问题在于定义对象client自动调用了Client类的构造函数此时需要读取对象server的数据但全局变量的不可控性让我们不能保证对象server在此时被读取时是初始化的。试想如果还有对象client1, client2等等不同的用户读写我们不能保证当前server的数据是我们想要的。解决方法: 将全局变量变为本地静态变量使用一个函数只用来定义一个本地静态变量并返回它的引用。因为C规定在本地范围(函数范围)内定义某静态对象时当此函数被调用该静态变量一定会被初始化。classServer{...};Serverserver(){//将直接的声明改为一个函数staticServer server;returnserver;}classClient{...};Client::client(){//客户端构造函数通过函数访问服务器数据numberserver().number;}Clientclient(){//同样将客户端的声明改为一个函数staticClient client;returnclient;}总结:对于自由类型要保证在定义时手动初始化在定义构造函数时要用初始化列表避免使用在函数体内的赋值初始化。在使用初始化列表时为了保持代码一致性初始化列表中变量的顺序 要与其声明顺序相同当不同的编译单元产生互动时要将其中非本地的静态变量变为本地的静态变量才能保证安全的读写也可以在client类里面私有成员变量定义一个sever对象在公司这样用过对引用的理解引用是无所有权无空值可能的“指针”总是指向别的对象。引用不对内存管理负责那总是别人的事无权即无责。成员变量也可以为引用我觉得应该少用使用起来风险较高可能会指向一个不存在的内存引起不确定性问题虽然编译器会做一定程度的检测但还是有绕过的可能举例classWidget{size_tcap;public:Widget(size_tcap):cap(cap){}size_tgetCap(){returncap;}};Widget*getWidget(){size_tcap(10);Widget*wnewWidget(cap);coutw-getCap()endl;// 打印10returnw;}intmain(intargc,constchar*argv[]){Widget*wgetWidget();coutw-getCap()endl;// 打印4301990432(随机值)return0;}

更多文章