对象构造的时候,对象成员变量的初始化顺序是什么样的 ?
派生类构造的时候,先构造基类还是先构造派生类 ?
构造函数中的初始化列表,初始化的顺序是列表的顺序吗 ?
析构的时候,析构的顺序是什么样的 ?
本文通过实例代码记录上述场景下的顺序。先说结论:
(1)全局对象以及静态全局对象,在 main 函数调用之前创建
(2)构造时,先构造类中的成员变量,再调用构造函数
(3)类中成员变量的构造顺序是变量在类中声明的顺序
(4)初始化列表中,成员变量的初始化顺序也是变量在类中声明的顺序
(5)对于派生类来说,先构造基类,再构造派生类
(6)对于多重继承的场景,基类构造的顺序是继承列表的顺序
(7)多重继承中如果有虚拟继承,那么先构造虚拟基类
(8)析构的顺序与构造的顺序是相反的
1 静态变量在 main 函数调用之前创建
main 函数是我们应用中第一个执行的函数,但却不是系统加载应用时第一个调用的函数。在 main 函数正式运行前,最基础的这个应用的环境变量如果需要配置则要进行配置;应用入参如果有需要也应该把入参放到规定的位置;对于全局变量和静态全局变量,也是在 main 函数初始化之前构造的。
#include <iostream>
#include <string>
#include <unistd.h>
class Test1 {
public:
Test1() {
std::cout << "Test1()" << std::endl;
}
~Test1() {
std::cout << "~Test1()" << std::endl;
}
};
class Test2 {
public:
Test2() {
std::cout << "Test2()" << std::endl;
}
~Test2() {
std::cout << "~Test2()" << std::endl;
}
private:
static Test1 t1;
};
Test1 Test2::t1;
Test1 t1;
static Test2 t2;
void Do() {
std::cout << "Do()" << std::endl;
static Test1 t1;
}
int main() {
std::cout << "main" << std::endl;
sleep(2);
static Test1 t1;
Do();
Do();
return 0;
}
运行结果如下,从结果中还可以看出,对于局部静态变量来说,是在函数第一次调用的时候构造的。这也体现了懒加载的思想,局部静态变量在第一次函数调用之前,根本就没有用,直接构造这个对象是浪费时间和内存资源。
使用 gdb 对 Test1 的构造函数设置断点,可以看到 Test1 的调用栈。
2 构造顺序和析构顺序
对于一个没有继承其它类的类来说,构造顺序有以下几点:
(1)构造的时候先构造类的成员变量,再调用构造函数。这也可以理解,符合我们的使用习惯,因为构造函数中可以使用这个对象中的成员变量,所以调用构造函数的时候,成员变量需要初始化好。
(2)成员变量的构造顺序(也叫初始化顺序)是变量在类中声明的顺序。可以猜测,编译器编译的时候是直接按顺序解析类中的成员变量,然后按这个顺序进行初始化的。这样也符合我们的使用习惯,在使用变量的时候,后声明的变量,可能会使用到之前声明的变量,所以从上向下进行初始化。
(3)成员变量的构造顺序和变量的权限是没有关系的,只有一个影响因素,那就是变量在类中的声明顺序。
静态成员是属于类的,不是属于对象的。
静态成员变量在进程中只有一份,被所有对象所共享。
静态成员在程序启动的时候就会创建,而不是在构造对象的时候再创建。
析构顺序,是构造顺序的逆:
(1)先调用析构函数,再析构类中的成员变量
(2)成员变量的析构顺序与变量在类中声明的顺序是相反的
如下是验证代码,代码中有 3 个类 Test1 ~ Test3。另外有 3 个类 Test4 ~ Test5,这 3 个类中有 3 个成员变量,分别是 Test1 ~ Test3,这 3 个变量的声明顺序是不一样的,访问权限也是不一样的。通过这个代码可以观察构造顺序和析构顺序。
#include <iostream>
#include <string>
class Test1 {
public:
Test1() {
std::cout << "Test1()" << std::endl;
}
~Test1() {
std::cout << "~Test1()" << std::endl;
}
};
class Test2 {
public:
Test2() {
std::cout << "Test2()" << std::endl;
}
~Test2() {
std::cout << "~Test2()" << std::endl;
}
};
class Test3 {
public:
Test3() {
std::cout << "Test3()" << std::endl;
}
~Test3() {
std::cout << "~Test3()" << std::endl;
}
};
class Test4 {
public:
Test4() {
std::cout << "Test4()" << std::endl;
}
~Test4() {
std::cout << "~Test4()" << std::endl;
}
Test1 test1;
Test2 test2;
Test3 test3;
};
class Test5 {
public:
Test5() {
std::cout << "Test5()" << std::endl;
}
~Test5() {
std::cout << "~Test5()" << std::endl;
}
Test3 test3;
Test2 test2;
Test1 test1;
};
class Test6 {
public:
Test6() {
std::cout << "Test6()" << std::endl;
}
~Test6() {
std::cout << "~Test6()" << std::endl;
}
private:
Test3 test3;
Test2 test2;
public:
Test1 test1;
};
int main() {
std::cout << "t4 --------" << std::endl;
Test4 t4;
std::cout << "t5 --------" << std::endl;
Test5 t5;
std::cout << "t6 --------" << std::endl;
Test6 t6;
std::cout << "----------------" << std::endl;
return 0;
}
运行结果如下:
如下代码,运行结果如下,从运行结果也可以看出来,在调用构造函数的时候,打印 a_ 是 10,这说明在调用构造函数的时候,成员变量已经完成了初始化。
#include <iostream>
#include <string>
class Test {
public:
Test() {
std::cout << "Test(), a_ = " << a_ << std::endl;
a_ = 100;
std::cout << "a_ = " << a_ << std::endl;
}
~Test() {
std::cout << "~Test()" << std::endl;
}
int a_ = 10;
};
int main() {
Test t;
return 0;
}
3 初始化列表
3.1 初始化列表初始化顺序
初始化列表的初始化顺序有以下几点需要注意:
(1)初始化列表中变量初始化的顺序,不是按照列表的顺序,仍然是按照成员变量在类中声明的顺序。
(2)初始化列表中的初始化也是在调用构造函数之前完成。
(3)如果类中的成员变量有的在初始化列表中进行初始化,有的不在初始化列表中进行初始化,整体的初始化顺序仍然是按照变量在类中声明的顺序。
(4)初始化列表中初始化的变量,不会重复初始化。也就是说不会在初始化列表初始化之前初始化一次,到初始化列表中再初始化一次。
如下代码中 Test3 初始化列表中的顺序和变量在类中声明的顺序不一样,变量的构造顺序仍然按照变量在类中声明的顺序初始化;Test5 中,初始化列表中只初始化了 test1,test1 和没有放到初始化列表中的 test2 和 tes3 的相对顺序仍然是它们在类中声明的顺序。
#include <iostream>
#include <string>
class Test1 {
public:
Test1() {
std::cout << "Test1()" << std::endl;
}
~Test1() {
std::cout << "~Test1()" << std::endl;
}
};
class Test2 {
public:
Test2() {
std::cout << "Test2()" << std::endl;
}
~Test2() {
std::cout << "~Test2()" << std::endl;
}
};
class Test3 {
public:
Test3() {
std::cout << "Test3()" << std::endl;
}
~Test3() {
std::cout << "~Test3()" << std::endl;
}
};
class Test4 {
public:
Test4() : test3(), test1(), test2() {
std::cout << "Test4()" << std::endl;
}
~Test4() {
std::cout << "~Test4()" << std::endl;
}
Test1 test1;
Test2 test2;
Test3 test3;
};
class Test5 {
public:
Test5() : test1() {
std::cout << "Test5()" << std::endl;
}
~Test5() {
std::cout << "~Test5()" << std::endl;
}
Test3 test3;
Test2 test2;
Test1 test1;
};
class Test6 {
public:
Test6() : test1(), test2(), test3() {
std::cout << "Test6()" << std::endl;
}
~Test6() {
std::cout << "~Test6()" << std::endl;
}
private:
Test3 test3;
Test2 test2;
public:
Test1 test1;
};
int main() {
std::cout << "t4 --------" << std::endl;
Test4 t4;
std::cout << "t5 --------" << std::endl;
Test5 t5;
std::cout << "t6 --------" << std::endl;
Test6 t6;
std::cout << "----------------" << std::endl;
return 0;
}
运行结果如下:
如下代码,两个成员变量 i 和 j,先声明的 i 后声明的 j,所以初始化顺序是先 i 后 j。在初始化列表中对 i 进行初始化的时候,j 还没有初始化,所以 i 是一个随机值,j 是 10。
#include <iostream>
#include <string>
class Test {
public:
Test(int a) : j(a), i(j) {
std::cout << "Test(), j = " << j << ", i = " << i << std::endl;
}
~Test() {
std::cout << "~Test()" << std::endl;
}
void Print() {
std::cout << "Print(), j = " << j << ", i = " << i << std::endl;
}
private:
int i;
int j;
};
int main() {
std::cout << "main" << std::endl;
Test t(10);
t.Print();
return 0;
}
运行结果如下:
3.2 const 变量和引用只能在初始化列表中初始化
(1)const 常量和引用必须在初始化列表中进行初始化,不能在构造函数中初始化
(2)const 常量只能在初始化列表中初始化,构造函数和其它地方不能修改
(3)引用在初始化列表中初始化之后,在其它地方也可以进行修改
#include <iostream>
#include <string>
class Test {
public:
Test(int a, int &b) : a_(a), b_(b) {
std::cout << "Test(), a = " << a_ << ", b = " << b_ << std::endl;
// a_ = a; 编译错误
b_ = b;
}
~Test() {
std::cout << "~Test()" << std::endl;
}
void Print() {
std::cout << "Print(), a = " << a_ << ", b = " << b_ << std::endl;
}
private:
const int a_;
int &b_;
};
int main() {
int a = 10;
int b = 20;
Test t(a, b);
t.Print();
return 0;
};
3.3 基类构造只能在初始化列表中初始化
#include <iostream>
#include <string>
class Base {
public:
Base(int a, int b) : a_(a), b_(b) {
std::cout << "Base(), a = " << a_ << ", b = " << b_ << std::endl;
}
~Base() {
std::cout << "~Base()" << std::endl;
}
void Print() {
std::cout << "Print(), a = " << a_ << ", b_ = " << b_ << std::endl;
}
public:
int a_;
private:
int b_;
};
class Derived : public Base {
public:
Derived(int data) : Base(data, data * 2), data_(data) {
// 这句,语法没有错,但是不能起到初始化基类的作用
// 只是生成了一个基类的临时对象
// 初始化基类只能在初始化列表中
Base(data * 10, data * 20);
std::cout << "Derived()" << std::endl;
};
~Derived() {
std::cout << "~Derived()" << std::endl;
}
private:
int data_;
};
int main() {
Derived d(10);
d.Print();
return 0;
}
4 继承
当派生类构造的时候,先构造基类,再构造派生类。这也是符合常识的,派生类是继承基类而来,没有构造出基类对象,何来派生类对象,派生类对象从哪继承。
4.1 单继承
如下代码,Derived 继承了 Base,构造 Derived 的时候,先构造 Base 再构造 Derived。
#include <iostream>
#include <string>
#include <unistd.h>
class Base {
public:
Base() {
std::cout << "Base()" << std::endl;
}
~Base() {
std::cout << "~Base()" << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived()" << std::endl;
}
~Derived() {
std::cout << "~Derived()" << std::endl;
}
};
int main() {
std::cout << "main" << std::endl;
Derived d;
return 0;
}
4.2 多继承
多重继承是一个类可以继承多个类。多重继承意思是一个类需要同时具备多个类的属性,从现实语义上来说,多重继承更像是组合的语义。多继承的语法和语义,让人感觉有点别扭,与常识也是不大符合的,但是 c++ 中支持这种语法。
4.2.1 构造顺序
(1)基类构造的顺序按继承列表的顺序进行构造,而不是按照初始化列表中声明的顺序进行构造
(2)如果有虚拟继承,那么先构造虚拟继承的基类
#include <iostream>
#include <string>
#include <unistd.h>
class Base1 {
public:
Base1(int a) : a_(a) {
std::cout << "Base1() a = " << a_ << std::endl;
}
~Base1() {
std::cout << "~Base1()" << std::endl;
}
int a_;
};
class Base2 {
public:
Base2(int a) : a_(a) {
std::cout << "Base2() a = " << a_ << std::endl;
}
~Base2() {
std::cout << "~Base2()" << std::endl;
}
int a_;
};
class Base3 {
public:
Base3(int a) : a_(a) {
std::cout << "Base3() a = " << a_ << std::endl;
}
~Base3() {
std::cout << "~Base3()" << std::endl;
}
virtual void Do3() {
std::cout << "Do3, a = " << a_ << std::endl;
}
int a_;
};
class Base4 {
public:
Base4(int a) : a_(a) {
std::cout << "Base4() a = " << a_ << std::endl;
}
~Base4() {
std::cout << "~Base4()" << std::endl;
}
virtual void Do4() {
std::cout << "Do4, a = " << a_ << std::endl;
}
int a_;
};
class Derived1 : public Base1, public Base2 {
public:
Derived1() : Base2(20), Base1(10) {
std::cout << "Derived1()" << std::endl;
}
~Derived1() {
std::cout << "~Derived1()" << std::endl;
}
};
class Derived2 : public Base1, public Base2 {
public:
Derived2() : Base1(100), Base2(200) {
std::cout << "Derived2()" << std::endl;
}
~Derived2() {
std::cout << "~Derived2()" << std::endl;
}
};
class Derived3 : public Base3, public Base4 {
public:
Derived3() : Base4(400), Base3(30) {
std::cout << "Derived3()" << std::endl;
}
~Derived3() {
std::cout << "~Derived3()" << std::endl;
}
};
class Derived4 : public Base1, public Base4, public Base3, virtual public Base2 {
public:
Derived4() : Base1(1000), Base2(2000), Base3(3000), Base4(4000) {
std::cout << "Derived4()" << std::endl;
}
~Derived4() {
std::cout << "~Derived4()" << std::endl;
}
};
int main() {
std::cout << "main" << std::endl;
std::cout << "d1 --------" << std::endl;
Derived1 d1;
std::cout << "d2 --------" << std::endl;
Derived2 d2;
std::cout << "d3 --------" << std::endl;
Derived3 d3;
std::cout << "d4 --------" << std::endl;
Derived4 d4;
return 0;
}
运行结果如下:
(1)Derived1 和 Derived2 都是多继承 Base1 和 Base2,继承列表中顺序是一致的,初始化列表中是不一样的。基类构造顺序都是继承列表的顺序。
(2)Derived4 虚拟继承了 Base2,并且 Base2 在继承声明的最后,构造 Derived4 的时候也是先构造 Base2。
4.2.2 多重继承的二义性以及怎么避免
如下代码,两个基类,Base1 和 Base2,两个基类有两个相同的函数 Speak()。Derived1 类继承自 Base1 和 Base2,当通过 Derived1 对象调用 Speak 的时候,会产生二义性。编译不知道调用的这个 Speak 是 Base1 中的还是 Base2 中的。
消除二义性,可以通过在调用的时候指定是哪个基类中的方法。
#include <iostream>
#include <string>
class Base1 {
public:
Base1() {
std::cout << "Base1()" << std::endl;
}
~Base1() {
std::cout << "~Base1()" << std::endl;
}
void Speak(std::string str) {
std::cout << "Base1 speak: " << str << std::endl;
}
};
class Base2 {
public:
Base2() {
std::cout << "Base2()" << std::endl;
}
~Base2() {
std::cout << "~Base2()" << std::endl;
}
void Speak(std::string str) {
std::cout << "Base2 speak: " << str << std::endl;
}
};
class Derived1 : public Base1, public Base2 {
public:
Derived1() {
std::cout << "Derived1()" << std::endl;
}
~Derived1() {
std::cout << "Derived1()" << std::endl;
}
};
int main() {
Derived1 d1;
// d1.Speak(); // 二义性
d1.Base1::Speak("hello");
return 0;
}
如下图所示,Derived1 和 Derived2 继承自 Base1,Derived3 继承自 Derived1 和 Derived2。这样当 Derived3 调用 Speak() 的时候就会产生二义性,因为 Speak 在 Derived1 和 Derived2 中各有一份。
有两种方法可以消除这个代码中的二义性,个人更倾向于使用第一种,这样语法清晰。
(1)代码中指定使用哪个类中的函数
d3.Derived1::Speak("hello");
(2)将 Derived1 和 Derived2 改成虚拟继承
#include <iostream>
#include <string>
class Base1 {
public:
Base1() {
std::cout << "Base1()" << std::endl;
}
~Base1() {
std::cout << "~Base1()" << std::endl;
}
void Speak(std::string str) {
std::cout << "Base1 speak: " << str << std::endl;
}
};
class Derived1 : virtual public Base1 {
public:
Derived1() {
std::cout << "Derived1()" << std::endl;
}
~Derived1() {
std::cout << "Derived1()" << std::endl;
}
};
class Derived2 : virtual public Base1 {
public:
Derived2() {
std::cout << "Derived2()" << std::endl;
}
~Derived2() {
std::cout << "Derived2()" << std::endl;
}
};
class Derived3 : public Derived1, public Derived2 {
public:
Derived3() {
std::cout << "Derived3()" << std::endl;
}
~Derived3() {
std::cout << "Derived3()" << std::endl;
}
};
int main() {
Derived3 d3;
d3.Speak("hello"); // 二义性
return 0;
}