背景
记录一些项目开发过程中遇到的问题,来加深印象和理解,一方面避免重复犯错,另一方面再次遇到可以快速定位问题。
记录
- C++单例类构造函数内打印代码没执行(成员变量类阻塞、成员变量初始化)
- 指针成员变量若直接在构造函数申请、析构函数释放,再涉及容器/拷贝构造,会导致资源不可用(C++默认函数、浅拷贝)
- C++格式化输出小数点后位数
二、问题列表
2.1 C++ 构造函数和析构函数 = default; 和空{}区别
在C++中,构造函数和析构函数可以被定义为= default;
或留空为{}
。这两种方式虽然看似相似,但实际上它们有着重要的区别,尤其是在编译器生成的行为和代码的安全性方面。
构造函数
= default;
当一个构造函数被定义为= default;
,你指示编译器自动生成一个默认构造函数。这意味着编译器将会:
- 如果类没有任何用户声明的构造函数,编译器会自动提供一个默认构造函数。
- 默认构造函数将初始化类中的所有非静态数据成员,使用它们的默认构造函数(如果成员类型有默认构造函数的话)。
- 如果类有用户声明的构造函数,但没有默认构造函数,
= default;
将显式地要求编译器生成一个。 - 如果类的成员中有任何类型不能被默认构造,或者类中定义了删除的默认构造函数,那么使用
= default;
将产生编译错误。
{}
当构造函数被定义为空{}
,你告诉编译器这是一个用户定义的构造函数,它不做任何事情。这意味着:
- 编译器不会自动生成任何代码,构造函数体内的代码将被执行(在这种情况下,没有代码,所以实际上什么也不做)。
- 不会调用成员的默认构造函数来初始化成员,除非显式地在初始化列表中这样做。
- 如果类有非默认构造的成员,它们将不会被初始化,除非你在构造函数的初始化列表中显式初始化它们。
- 这样的构造函数可以被定义多次,只要它们的参数列表不同,而
= default;
的构造函数只能有一个。
析构函数
= default;
当析构函数被定义为= default;
,你告诉编译器自动生成一个默认析构函数。这意味着:
- 析构函数将被调用来清理类中的资源。
- 它会调用所有非静态数据成员的析构函数,按照它们在类中声明的逆序。
- 如果成员类型有析构函数,它们将被调用。
{}
当析构函数被定义为空{}
,这意味着:
- 用户定义了一个空的析构函数体,这通常意味着类没有需要特殊清理的资源。
- 成员的析构函数仍然会被调用,因为这是C++析构函数的标准行为,即使用户定义的析构函数体是空的。
总结
使用= default;
是告诉编译器“按预期行为生成代码”,而使用{}
是告诉编译器“我来控制这里发生什么”。= default;
提供了编译器的自动化和安全保证,而空{}
则需要程序员自己负责初始化和清理工作。在现代C++编程实践中,推荐尽可能使用= default;
,除非有特殊需求去显式地控制构造函数和析构函数的行为。