共计 1865 个字符,预计需要花费 5 分钟才能阅读完成。
对象生命周期的起点:构造函数调用
在 C ++ 中,对象的生命周期始于构造函数的调用。当创建一个类对象时,编译器会自动调用对应的构造函数进行初始化。构造函数的主要职责是确保对象处于有效状态,包括成员变量的初始化和资源的获取。

- 构造函数的调用时机
- 显式创建对象时(如
MyClass obj;) - 通过 new 动态分配对象时
-
作为其他类的成员对象被创建时
-
构造函数的执行顺序
- 基类构造函数(如果有继承关系)
- 成员变量的初始化(按声明顺序)
- 构造函数体内的代码
成员初始化列表的重要性
成员初始化列表是构造函数中特别重要的部分,它决定了成员变量的初始化方式。
- 必须使用初始化列表的情况:
- 初始化 const 成员
- 初始化引用成员
-
初始化没有默认构造函数的类成员
-
初始化顺序注意点:
成员变量的初始化顺序只与它们在类中的声明顺序有关,与初始化列表中的顺序无关。这是一个常见的陷阱。
普通成员函数的调用
在对象构造完成后,就可以正常调用各种成员函数了。成员函数的调用遵循以下规则:
- 普通成员函数:通过对象或指针直接调用
- const 成员函数:可以在 const 对象上调用
- static 成员函数:不依赖对象实例,通过类名调用
虚函数的特殊行为
虚函数在构造函数中的行为需要特别注意:
- 在构造函数中调用虚函数时,调用的是当前类的实现,而不是派生类的
- 这是因为在构造函数执行时,派生类部分尚未构造完成
- 这一行为是 C ++ 标准明确规定的,目的是确保对象构造期间的稳定性
对象生命的终点:析构函数
当对象生命周期结束时,析构函数会被自动调用:
- 析构函数的调用顺序
- 析构函数体内的代码
- 成员变量的析构(按声明逆序)
-
基类析构函数(如果有继承关系)
-
析构函数调用时机
- 对象离开作用域时
- delete 操作时
- 作为成员对象的包含对象被销毁时
完整代码示例
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor\n";}
virtual ~Base() { std::cout << "Base destructor\n";}
virtual void foo() { std::cout << "Base::foo\n";}
void bar() { std::cout << "Base::bar\n";}
};
class Derived : public Base {
int m_x;
std::string m_name;
public:
Derived(int x, const std::string& name) : m_x(x), m_name(name) {
std::cout << "Derived constructor\n";
foo(); // 调用的是 Derived::foo() 吗?}
~Derived() override { std::cout << "Derived destructor\n";}
void foo() override { std::cout << "Derived::foo with" << m_name << "\n";}
};
int main() {Derived d(42, "test");
d.bar();
return 0;
}
常见陷阱分析
- 基类与派生类的构造 / 析构顺序
- 构造顺序:基类→派生类
- 析构顺序:派生类→基类
-
如果顺序错误可能导致资源泄漏或未定义行为
-
虚函数在构造函数中的行为
- 如前所述,构造函数中调用虚函数不会多态
-
这是 C ++ 有意为之的设计,并非缺陷
-
成员变量初始化顺序依赖
- 成员变量按声明顺序初始化
- 初始化列表中的顺序不影响实际初始化顺序
- 避免让成员变量的初始化相互依赖
对象生命周期流程图(文字描述)
- 构造阶段:
- 分配内存
- 调用基类构造函数
- 初始化成员变量(按声明顺序)
-
执行构造函数体
-
使用阶段:
- 调用各种成员函数
-
对象状态可能改变
-
析构阶段:
- 执行析构函数体
- 销毁成员变量(逆序)
- 调用基类析构函数
- 释放内存
最佳实践
- 构造函数中避免调用虚函数
- 如果必须调用,可以考虑使用模板方法模式
-
或者将初始化逻辑移到单独的 init 函数中
-
避免成员变量初始化顺序依赖
- 保持成员变量独立性
-
必要时使用指针延迟初始化
-
使用 RAII 管理资源
- 资源获取即初始化
- 确保资源在析构时自动释放
- 使用智能指针管理动态内存
思考题
- 在多继承场景下,构造顺序如何确定?
- 继承列表中基类的顺序如何影响构造顺序?
-
虚继承又会带来什么变化?
-
异常抛出对析构函数调用有何影响?
- 如果构造函数抛出异常,析构函数会被调用吗?
- 已经构造完成的成员变量会怎样?
总结
理解 C ++ 类成员函数的调用顺序是编写健壮、可靠代码的基础。通过掌握构造、析构的顺序规则,可以避免许多常见的错误和陷阱。记住对象生命周期的各个阶段,合理设计类的结构,将使你的 C ++ 代码更加安全和高效。
正文完
