成员初始化列表
1. 何时必须用成员初始化列表
- 如果这个成员是个引用;
- 如果是个const类型成员;
- 如果这个类是继承一个基类,并且基类中有构造函数,这个构造函数里边还有参数;
- 如果你的成员变量类型是某个类类型,而这个类的构造函数带参数时;
2. 使用初始化列表的优势
2.1 没有使用初始化列表
#include<iostream>
using namespace std;
class X {
public:
int m_value;
public:
X() {
m_value = 0;
printf("this = %p ", this);
printf("X()默认构造函数被调用\n");
}
X(int value)
: m_value(value) {
printf("this = %p ", this);
printf("X(int)构造函数被调用\n");
}
X(const X& other)
: m_value(other.m_value) {
printf("this = %p ", this);
printf("X拷贝构造函数被调用\n");
}
X& operator=(const X& other) {
m_value = other.m_value;
printf("this = %p ", this);
printf("X拷贝赋值函数被调用\n");
return *this;
}
~X() {
printf("this = %p ", this);
printf("X析构函数被调用\n");
}
};
class A {
public:
int m_aElement;
X objX;
public:
A(int value) {
objX = value;
m_aElement = 500;
}
};
int main()
{
A objA(1000);
}
输出:
this = 0x7ffff9c8b054 X()默认构造函数被调用
this = 0x7ffff9c8b024 X(int)构造函数被调用
this = 0x7ffff9c8b054 X拷贝赋值函数被调用
this = 0x7ffff9c8b024 X析构函数被调用
this = 0x7ffff9c8b054 X析构函数被调用
查看汇编代码:
Dump of assembler code for function A::A(int):
=> 0x00005555555553aa <+0>: endbr64
0x00005555555553ae <+4>: push rbp
0x00005555555553af <+5>: mov rbp,rsp
0x00005555555553b2 <+8>: push rbx
0x00005555555553b3 <+9>: sub rsp,0x28
0x00005555555553b7 <+13>: mov QWORD PTR [rbp-0x28],rdi
0x00005555555553bb <+17>: mov DWORD PTR [rbp-0x2c],esi
0x00005555555553be <+20>: mov rax,QWORD PTR fs:0x28
0x00005555555553c7 <+29>: mov QWORD PTR [rbp-0x18],rax
0x00005555555553cb <+33>: xor eax,eax
0x00005555555553cd <+35>: mov rax,QWORD PTR [rbp-0x28]
0x00005555555553d1 <+39>: add rax,0x4
0x00005555555553d5 <+43>: mov rdi,rax
# 最开始就调用X的默认构造函数将 objX 构造出来了
0x00005555555553d8 <+46>: call 0x5555555552a2 <X::X()>
0x00005555555553dd <+51>: mov edx,DWORD PTR [rbp-0x2c]
0x00005555555553e0 <+54>: lea rax,[rbp-0x1c]
0x00005555555553e4 <+58>: mov esi,edx
0x00005555555553e6 <+60>: mov rdi,rax
# 调用X::X(int)构造一个临时对象
0x00005555555553e9 <+63>: call 0x5555555552e4 <X::X(int)>
0x00005555555553ee <+68>: mov rax,QWORD PTR [rbp-0x28]
0x00005555555553f2 <+72>: lea rdx,[rax+0x4]
0x00005555555553f6 <+76>: lea rax,[rbp-0x1c]
0x00005555555553fa <+80>: mov rsi,rax
0x00005555555553fd <+83>: mov rdi,rdx
# 调用拷贝赋值函数将临时对象赋值给 objX
0x0000555555555400 <+86>: call 0x555555555328 <X::operator=(X const&)>
0x0000555555555405 <+91>: lea rax,[rbp-0x1c]
0x0000555555555409 <+95>: mov rdi,rax
# 将临时对象析构
0x000055555555540c <+98>: call 0x555555555372 <X::~X()>
0x0000555555555411 <+103>: mov rax,QWORD PTR [rbp-0x28]
0x0000555555555415 <+107>: mov DWORD PTR [rax],0x1f4
0x000055555555541b <+113>: nop
0x000055555555541c <+114>: mov rax,QWORD PTR [rbp-0x18]
0x0000555555555420 <+118>: xor rax,QWORD PTR fs:0x28
0x0000555555555429 <+127>: je 0x555555555469 <A::A(int)+191>
0x000055555555542b <+129>: jmp 0x555555555464 <A::A(int)+186>
0x000055555555542d <+131>: endbr64
2.2 使用初始化列表
/* ************************************************************************
> File Name: test.cpp
> Author: niu0217
> Created Time: Tue 30 Jul 2024 07:59:02 AM CST
> Description:
************************************************************************/
// g++ -g -o test test.cpp
#include<iostream>
using namespace std;
class X {
public:
int m_value;
public:
X() {
m_value = 0;
printf("this = %p ", this);
printf("X()默认构造函数被调用\n");
}
X(int value)
: m_value(value) {
printf("this = %p ", this);
printf("X(int)构造函数被调用\n");
}
X(const X& other)
: m_value(other.m_value) {
printf("this = %p ", this);
printf("X拷贝构造函数被调用\n");
}
X& operator=(const X& other) {
m_value = other.m_value;
printf("this = %p ", this);
printf("X拷贝赋值函数被调用\n");
return *this;
}
~X() {
printf("this = %p ", this);
printf("X析构函数被调用\n");
}
};
class A {
public:
int m_aElement;
X objX;
public:
A(int value)
: m_aElement(100), objX(value) {
}
};
int main()
{
A objA(1000);
}
输出:
this = 0x7fff72ea7b24 X(int)构造函数被调用
this = 0x7fff72ea7b24 X析构函数被调用
汇编代码:
Dump of assembler code for function A::A(int):
=> 0x00005555555552fe <+0>: endbr64
0x0000555555555302 <+4>: push rbp
0x0000555555555303 <+5>: mov rbp,rsp
0x0000555555555306 <+8>: sub rsp,0x10
0x000055555555530a <+12>: mov QWORD PTR [rbp-0x8],rdi
0x000055555555530e <+16>: mov DWORD PTR [rbp-0xc],esi
0x0000555555555311 <+19>: mov rax,QWORD PTR [rbp-0x8]
0x0000555555555315 <+23>: mov DWORD PTR [rax],0x64
0x000055555555531b <+29>: mov rax,QWORD PTR [rbp-0x8]
0x000055555555531f <+33>: lea rdx,[rax+0x4]
0x0000555555555323 <+37>: mov eax,DWORD PTR [rbp-0xc]
0x0000555555555326 <+40>: mov esi,eax
0x0000555555555328 <+42>: mov rdi,rdx
0x000055555555532b <+45>: call 0x555555555282 <X::X(int)>
0x0000555555555330 <+50>: nop
0x0000555555555331 <+51>: leave
0x0000555555555332 <+52>: ret
End of assembler dump.
3. 注意事项
成员初始化列表是按照变量定义顺序来初始化的,所以一定要注意!!!
代码:
#include<iostream>
using namespace std;
class A {
public:
int m_aElement;
int m_bElement;
A() : m_bElement{1000}, m_aElement{m_bElement} {
}
};
int main()
{
A objA;
cout<<objA.m_aElement<<endl; //32765
cout<<objA.m_bElement<<endl; //1000
}
我们希望两个都是1000,但是这段代码会先给m_aElement
进行赋值,因为m_aElement
先定义,但是这个时候m_bElement
没有赋值,所以是个随机值,因此不会是1000。而接下来给m_bElement
赋值变成1000,不会影响到m_aElement
。