美文网首页
Effective c++ 学习笔记(item4)

Effective c++ 学习笔记(item4)

作者: 懒生活 | 来源:发表于2021-08-28 08:14 被阅读0次

Effective c++ 学习笔记(item4)

item4: make sure that objects are initialized before they're used.
对于内置型对象,需要手动进行初始化,如int x = 0; 对于非内置的对象,初始化的任务落在了构造函数上。在进入构造函数前,类会按照声明顺序进行类成员对象的初始化。进入构造函数时,如果对成员对象继续进行赋值,那是严格上说不叫初始化过程,而是赋值过程。

类的初始化和类成员对象的初始化

通过下面的例子可以了解类的初始化,和类内部成员的初始化过程。

class Position
{
    int x,y;
    string str;
};
int main()
{
    Position p;
    cout<<p.x<<endl;
    return 0;
} 

程序在实例化Position对象p的时候,先进入对象内部对该对象类成员对象先进行初始化,逐层递归。这里按顺序先进行x,y的定义。这里x,y是内置变量c++没有机制去调用他们的构造函数进行初始化因为他们没有构造函数。所以此时的x,y是没有初始化过的!按顺序定义到str的时候,因为str是string对象,c++会在定义str的时候调用string对象默认的构造函数对str进行初始化。

有两种方式对类成员对象进行初始化。

  • 一种是没有初始化列表存在的情况下, 程序会调用类对象的默认构造函数进行初始化。
  • 如果有初始化列表存在,会取用初始化列表的值,通过拷贝构造函数构造出类成员对象。
  • 这里的区别是一个是调用默认构造函数,一个是调用拷贝构造函数。 举例说明这两种情况,详细区别说明见代码中的注释。
//不带初始化参数列表的例子
class ClassRoom
{
public: 
ClassRoom(const string&name, int memberUpLimit) {classname = classname; limit=memberUpLimit; }
string classname;
int limit;
}
int main()
{
ClassRoom oneRoom("class1",50);
/*编译器在实例化oneRoom的时候,也就是要为oneRoom对象分配内存的时候,先要进入类定义,把类成员对象按顺序定义出来。首先是定义成员对象classname,这个是类对象,会调用默认构造函数进行初始化,然后是limit先分配内存,但因为这个是内置变量,没有构造函数可以调用,所以只是分配了内存没有初始化。定义完成员变量后,编译器才会执行oneRoom的构造函数。 在oneRoom的构造函数中有语句`classname = classname; limit=memberUpLimit;`,这些语句是对成员变量的赋值而不是初始化动作。如上述成员对象classname在定义的时候已经用默认构造函数初始化过一次,初始赋值为空而已,都了这已经不是初始化,而是赋值含义。*/
}

```c
//带初始化参数列表的例子
class ClassRoom
{
public: 
ClassRoom(const string&name, int memberUpLimit) 
:classname(name),limit(memberUplimit)
{}
string classname;
int limit;
}
int main()
{
ClassRoom oneRoom("class1",50);
/*
在进入classroom的构造函数之前,系统调用classname的拷贝构造函数,一次性把程序员想要的初值设定到位了。这样就不需要在其他地方在赋值一次,这样相对于没有初始化列表的代码会少一次设置值的操作,在效率上是值得提倡的 
*/
}

另外需要注意的是:

  • 考虑效率原因之外还有一种情况是必须使用初始化列表的。比如类里面如果包含有const成员对象。那么const成员对象在初始化之后是不允许赋值的,此时就只能依赖于初始化列表的方式实现初始化了。但是在最新的编译器c++11标准的情况下是有些例外的,一些const 内置变量甚至是const string对象也是支持赋值初始化(不用再借助初始化列表)这个我在item2中详细举例描述过。
    类成员对象的初始化顺序是严格按照声明的顺序进行的。跟初始化列表里面的顺序无关。

static对象的初始化顺序

文中所说的static对象指的是全局变量/对象或者static修饰的变量/对象

这一章后面讲到static对象的初始化顺序,这一部分的讲解不好理解。首先要明白这一部分提到的static对象并不是只指用static修饰的对象。文中提到的static对象含义是在构造之后,一直到整个程序结束后,会被程序自动释放的对象。所以函数内的栈对象不是这个范畴,因为栈对象在函数退出后会释放。new出来的heap-base对象生存期依赖于何时调用delete,这个也不在这个范畴。像全局对象,或者用static修饰的对象便是这里所谓的static对象。

举例说明static对象初始化会遇到的问题

所有问题的根本原因是,c++不保证static对象的初始化顺序。解决技巧的依据是对于local-static对象,C++保证在函数进去后,第一次碰到前一定先把他初始化完成掉。

//在fs.h文件中写如下代码:
class FileSystem
{
public:
    FileSystem(int sz):size(sz){};
    int size;
}:
//在fs.cpp文件中写如下代码:
#include "fs.h"
FileSystem gFs(100);
//在main.cpp函数中写如下代码:
#include <iostream>
#include "fs.h"
extern FileSystem gFs;
class DirSystem
{
public:
    DirSystem(){size = gFs.size;}
    int size;
};
DirSystem gDir;
int main(){
cout <<gDir.size<<endl;
return 0;

这个程序是可以编译的,而且运行结果有可能也是正确的。存在的问题需要跟随下面的描述来认识。当程序进入main函数的时候,所有的全局变量已经被构造完毕。这包括这段代码中要到的全局对象gDir和gFs两个对象,但问题是c++不保证全局对象定义顺序,因为这两个定义在不同的文件中。那么如果c++编译时先定义gDir就会出现问题,因为gDir用到了gFs的初始化值,但正因为c++不能保证static对象的初始化顺序,也就是说,在初始化gDir的时候gFs可能只是外部符号占位extern FilejSystem gFs,所以gDir中用到的gFs.size可能是个随机值。
为了避免这个问题,用下面的技巧解决,就是把所有non-local的static对象全部定义成local static对象。

//在fs.h文件中写如下代码:
class FileSystem
{
public:
    FileSystem(int sz):size(sz){};
    int size;
}:
//在fs.cpp文件中写如下代码:
//相对于上面的实现这里的区别是,用一个全局函数取代了全局对象的定义,把全局的non_local 对象放到了函数里面变成了local static对象。
#include "fs.h"
//FileSystem gFs(100);
FileSystem& getGlobalFs()
{
static FileSystem gFs(100);
return gFs;
}
//在main.cpp函数中写如下代码:
#include <iostream>
#include "fs.h"
extern FileSystem& getGlobalFs();
class DirSystem
{
public:
    DirSystem(){size = getGlobalFs().size;}
    int size;
};
DirSystem gDir;
int main(){
cout <<gDir.size<<endl;
return 0;

在这份实现中,当初始化gDir的时候,不管gFs是否已经初始化,gDir调用全局函数getGlobalFs()的时候,遇到local static对象gFs,此时c++对于local成员,他能够保证如果没有初始化,会进行一次初始化。

相关文章

网友评论

      本文标题:Effective c++ 学习笔记(item4)

      本文链接:https://www.haomeiwen.com/subject/chqbiltx.html