C++成员初始化列表

我们在类的构造函数中使用成员初始化列表可以带来效率上的提升,那么成员初始化列表在编译后会发生什么就是这篇文章要探究的问题

文章目录


引入

考虑下面这个例子

cpp 复制代码
#include <iostream>
using namespace std;


class String {
    int len;
public:
    String() {
		len = 0;
	}
    String(int l) {
        len = l;
    }

    String(const String& str) {
		len = str.len;
	}
};

class Word {
    String _name;
    int _cnt;
public:
    Word() {
        _name = 0;
        _cnt = 0;
    }
};
int main()
{
    Word word;
}

其在VS2022下的汇编码,可以看到在 Word 的默认构造函数中调用了 String 的两个构造函数,分别是默认构造函数和带参数的构造函数

这里在 [rbp+0C4h] 的位置即 _name=0 右边的 0 作为临时 String 类对象,调用带参数的构造函数,然后将 [rbp+0C4h] 里的值逐位拷贝至 [this] 位置(首位置就是成员 _name的位置),相当于是编译器合成了默认的赋值运算符

其编译器对上述Word默认构造函数中代码的扩张结果可能如下:

cpp 复制代码
Word::Word()
{
	_name.String::String();	
	String temp = String( 0 );
	_name.String::operator=( temp );
	temp.String::~String();

	_cnt = 0;
}

成员初始化列表

用成员初始化列表优化上面的代码

上面的代码我们可以用成员初始化列表进行优化

cpp 复制代码
Word::Word : _name( 0 )
{
	_cnt = 0;
}

它会被扩张成下面的样子

cpp 复制代码
Word::Word()
{
	_name.String::String( 0 );
	_cnt = 0;
}

汇编代码可以看到只调用了一次带参的构造函数,且可以看到是在 _cnt=0 之前先进行了 _name 的初始化


成员初始化列表展开

当然我们也可以将两个变量全用成员初始化列表实现

cpp 复制代码
Word::Word() : _cnt(0), _name(0)
{}

编译器会逐个操作初始化列表,以适当的次序在构造函数内安插初始化操作,并且位于任何显式的用户代码之前,例如上面的代码会扩充为:

cpp 复制代码
Word::Word()
{
	_name.String::String( 0 );
	_cnt = 0;
}

可以看到扩充代码是以类中成员变量的声明次序决定,不是由初始化列表的排列次序决定 ,下面的汇编代码也验证了这一点。


成员初始化列表的潜在危险

如果类中成员变量的声明次序与初始化列表中的项目排列次序是混乱的或是有差异的,可能会导致意想不到的问题。

考虑下面这个例子:

cpp 复制代码
class X {
public:
    int i;
    int j;
public:
    X(int val) : j(val), i(j) {}
};

我们在写这个构造函数的本意可能是想先用 val 初始化 j 再用 j 来初始化 i,我们用 1 来初始化,然后输出 x.ix.j

可以看到结果不是我们希望的那样,因为正如前面所述,成员初始化列表会以变量声明顺序进行初始化,而不是初始化列表中的排列顺序。

我们可以改为下面的代码,就没有问题,因为合成的代码会插入到用户显式的代码之前。

cpp 复制代码
class X {
public:
    int i;
    int j;
public:
    X(int val) : j(val) {
		i = j;
	}
};

如果一个派生类成员函数被调用,其返回值当作基类构造函数的一个参数,将会如何:

cpp 复制代码
class FooBar : public X {
	int _fval;
public:
	int fval() {return _fval;}
	FooBar( int val ) : _fval( val ), X ( fval() ) {} 
}

它的可能扩张结果如下,也不是一个好主意

cpp 复制代码
FooBar::FooBar ()
{
	X::X( this, this->fval() );
	_fval = val;
}

以 1 作为初始化参数,然后顺序输出 _fvalij 结果如下


参考资料

《深度探索C++对象模型》------ Stanley B.Lippman著,侯捷译

相关推荐
晓纪同学41 分钟前
QT-简单视觉框架代码
开发语言·qt
威桑41 分钟前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略
飞飞-躺着更舒服44 分钟前
【QT】实现电子飞行显示器(简易版)
开发语言·qt
明月看潮生1 小时前
青少年编程与数学 02-004 Go语言Web编程 16课题、并发编程
开发语言·青少年编程·并发编程·编程与数学·goweb
明月看潮生1 小时前
青少年编程与数学 02-004 Go语言Web编程 17课题、静态文件
开发语言·青少年编程·编程与数学·goweb
Java Fans1 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手1 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
码农君莫笑1 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
Chinese Red Guest2 小时前
python
开发语言·python·pygame
一棵星2 小时前
Java模拟Mqtt客户端连接Mqtt Broker
java·开发语言