---
title: "C++中对象生命周期的一些理解"
date: 2026-08-31
category: "C++"
tags: ["C++", "类"]
summary: "引言\\n\\n对象在什么时候被创建，又在什么时候被销毁，这个问题在学习变量作用域时似乎很好回答，但一旦引入new、拷贝构造和函数返回，事情就变得没那么直观了。\\n\\n在此篇文章中，我将记录下自己通过一个观察实验对对象生命周期的理解，以供思考，如有不足之处欢迎指正。…"
series:
  name: "C++特性及用法"
  order: 0
---

## 引言

对象在什么时候被创建，又在什么时候被销毁，这个问题在学习变量作用域时似乎很好回答，但一旦引入new、拷贝构造和函数返回，事情就变得没那么直观了。

于是我写了一个带"观测仪器"的类，让构造、拷贝、析构都在发生时自己说话，通过输出的顺序来观察对象的一生。

在此篇文章中，我将记录下自己对对象生命周期的理解，以供思考，如有不足之处欢迎指正。

## 正文

### 会说话的类

实验的核心是一个在构造、拷贝、析构时都打印信息的`Test`类：

```cpp
class Test {
private:
    std::string m_name{};
    unsigned int m_id{};

public:
    Test() : Test{""} {}//委托构造，交给带参版本
    //explicit确保显式构造，不允许隐式类型转换
    explicit Test(const std::string &name, const unsigned int &id = 0)
        : m_name{name}, m_id{id} { cout << m_name << " has been created" << endl; }

    Test(const Test &test)
        : m_name{test.m_name}, m_id{test.m_id} { cout << m_name << " has been copied" << endl; }

    ~Test() { cout << m_name << " has been deleted" << endl; }
};
```

这么看来，只要在`main`里按不同方式创建对象，观察打印顺序，生命周期就一目了然了。

### 栈与堆上的对象

先看两种最基本的创建方式：

```cpp
Test *test1 = new Test{"second"};//堆上创建，不会自动销毁
Test test3{"third", 3};          //栈上创建，作用域结束时自动销毁

delete test1;//必须手动delete，否则泄漏
```

堆上的对象在`new`时构造，在`delete`时析构，完全由我们掌控。
栈上的对象则由编译器管理，`main`结束出作用域时按构造的**逆序**依次析构。

> 类内成员`m_name{}`、`m_id{}`用了类内初始化，配合委托构造`Test() : Test{""} {}`，默认构造的对象也有确定的初始状态。

### 拷贝发生的时机

拷贝构造的存在，让"创建"这个动作多了一种来源：

```cpp
Test test2{*test1};//用已有对象拷贝构造，打印copied

Test test4{add(*test1, test2)};//函数返回值继续拷贝构造test4
```

而`add`函数内部还有好几次创建与拷贝：

```cpp
static Test add(const Test &test1, const Test &test2)
{
    std::string name{test1.getName() + test2.getName()};
    unsigned int id{test1.getId() + test2.getId()};
    Test test{name, id};      //局部对象，函数结束时销毁
    Test testtest{test};      //一次多余的拷贝，仅作观察用
    return test;              //按值返回，发生拷贝（或被优化）
}//test和testtest在这里析构
```

从输出能明显看出，`add`内的局部对象在函数返回时析构，而返回出去的那份则由调用方接手，继续活到`main`结束。
另外形参用的是`const Test &`引用传参，所以传参本身不产生拷贝。

> 现代编译器大多会做返回值优化（RVO），`return test`可能连拷贝都省掉，但从语义上讲按值返回就是一次拷贝构造，这个概念还是应该建立起来。

### 析构顺序

把输出串起来看，`main`里创建的顺序是`test1`、`test2`、`test3`、`test4`，而析构时`test4`最先消失，`test3`次之，最后轮到`test2`。

**栈上对象的析构顺序与构造顺序严格相反**，像一层层退栈。
而`test1`在中间就被我们手动`delete`了，它的析构时机完全由代码决定，这也是堆对象需要小心的地方。

## 小结

对象的生命周期由它的创建方式决定：栈对象跟着作用域走，构造逆序析构，堆对象跟着`new`和`delete`走，拷贝则在传值、返回和显式拷贝构造的地方发生。让类在关键节点打印信息，是观察这些机制最直接的办法。

生命周期看似是入门话题，但把new、拷贝和返回串起来看一遍之后，才算是真正在脑子里有了画面。

本文所用的测试代码收录于[Learncpp](https://github.com/ManJin03/Learncpp)，如有不妥之处欢迎指正。
