编程后端C++开发者

C++11及更高版本中,如何使用成员初始化器初始化类成员?内联初始化、构造函数初始化列表和构造函数体内的声明有什么区别?

用 Hintsage AI 助手通过面试

回答。

问题的背景:

在经典C++中,类成员只能在构造函数的初始化列表中进行初始化。从C++11开始,可以直接在类内声明中指定默认值(成员初始化器),以提高代码的可读性和安全性。

问题:

有几种方法可以给类成员设置值:直接在声明中(类内)、通过构造函数的初始化列表和在构造函数体内。不同的方法会影响性能和语义;理解不当会导致不必要的复制或默认析构函数的问题,以及常量和引用的错误。

解决方案:

  1. 类内初始化器 — 对于简单的默认值推荐使用(只适用于C++11及更高版本)。在类内:
class MyClass { int x = 42; };
  1. 构造函数初始化列表 — 对于没有默认构造函数、常量和引用的类成员是必要的:
class MyClass { const int y; MyClass(int val) : y(val) {} // 否则会报编译错误 };
  1. 在构造函数体内初始化 — 不推荐使用,因为在此时成员数据已经通过默认构造函数创建,造成了不必要的操作:
class MyClass { std::string s; MyClass() { s = "hello"; } // 首先是默认构造,然后赋值 };

关键特点:

  • 类内初始化器简化了默认值的设置。
  • 初始化列表提供了控制初始化顺序的能力(对于常量和引用至关重要)。
  • 在构造函数体内初始化是对没有默认构造函数的类成员的一种反模式。

调皮的问题。

成员的初始化顺序是按照在类中声明的顺序,还是按照初始化列表的顺序?

初始化顺序始终是按照成员在类中声明的顺序,而不是按照初始化列表的顺序。顺序混乱对依赖成员是危险的。

class A { int x = 1; int y = 2; A() : y(10), x(20) {} }; // x在y之前初始化,尽管在列表中的顺序不同

如果构造函数体内尝试初始化常量成员,但没有在初始化列表中初始化,可以吗?

不可以。常量只能在初始化列表中初始化。在构造函数体内赋值将导致编译错误。

如果通过类的类内初始化器为成员设置默认值,并在构造函数的初始化列表中重写该值,会发生什么?

将使用初始化列表中的值。默认值仅在列表中未指明时使用。

class C { int x = 10; C() : x(20) {} // x将等于20 };

常见错误和反模式

  • 在构造函数体内初始化复杂的成员,而不是使用初始化列表。
  • 违反成员定义顺序和它们的初始化顺序。
  • 尝试在初始化列表外初始化常量或引用。

生活中的例子

负面案例

类处理文件。文件被声明为std::ofstream,并在构造函数体内初始化。风险:在默认构造函数中可能会创建无效的std::ofstream,导致文件操作错误。

优点:

  • 实现简单。

缺点:

  • 不必要的复制或创建无效对象。
  • 常量/引用成员时出现错误。

正面案例

文件在初始化列表中初始化,防止错误地使用处于无效状态的文件,而使用类内初始化器的默认数据成员。

优点:

  • 明确、可靠和有效的对象创建。
  • 对常量/引用的安全性。
  • 没有双重初始化。

缺点:

  • 当构造函数数量较多时,初始化列表代码会重复。