对象生命周期:地址还在不代表对象还活着 / Object Lifetime
生命周期是 C++ 内存管理里最容易被低估的概念。
很多错误不是“没有内存”,而是“那块内存上已经没有你以为的那个对象”。
1. 生命周期是什么
对象生命周期回答:
text
这个对象从什么时候开始存在?
到什么时候结束存在?
在这段时间内能不能按它的类型合法访问?看代码:
cpp
void f() {
int x = 42;
}x 的生命周期大致是:
text
进入声明位置
|
v
构造或初始化 x
|
v
x 可以被合法使用
|
v
离开作用域
|
v
x 生命周期结束对于内置类型,构造和析构不明显。
对于类类型,会更清楚:
cpp
#include <iostream>
struct Trace {
Trace() { std::cout << "construct\n"; }
~Trace() { std::cout << "destroy\n"; }
};
void f() {
Trace t;
}运行 f() 会看到构造和析构的顺序。
2. 作用域、存储期、生命周期不要混在一起
三个词要分开:
text
scope
名字在哪里能被使用
storage duration
对象所占存储能维持多久
lifetime
对象本身什么时候开始和结束例子:
cpp
int* p = nullptr;
{
int x = 1;
p = &x;
}花括号结束后:
text
x 这个名字不可见
x 对象生命周期结束
p 这个变量仍然活着
p 保存的地址没有自动清零于是 *p 是错误的。
3. 临时对象和引用
看代码:
cpp
#include <string>
std::string make_name() {
return "Alice";
}
int main() {
const std::string& ref = make_name();
}这里 make_name() 返回一个临时结果。
绑定到 const std::string& ref 时,临时对象的生命周期会延长到 ref 的生命周期结束。
但不要把这个规则扩大化。
下面就危险:
cpp
#include <string>
const std::string& bad() {
return std::string("Alice");
}函数返回时,临时对象不能被这样安全地延长给调用者。
调用者拿到的是悬垂引用。
4. string_view 不拥有字符
std::string_view 经常让人踩坑。
cpp
#include <string>
#include <string_view>
std::string_view title() {
std::string s = "C++";
return s;
}s 拥有字符。
string_view 只是观察字符。
text
inside title()
s object
|
v
characters: C + +
^
|
string_view
after title() returns
s destroyed
characters no longer owned by living string
string_view still stores old address and length所以:
text
view 不延长被观察对象的生命周期。同类概念还有 std::span<T>。
它观察一段连续对象,但不拥有那段对象。
5. 容器中的引用也会失效
看代码:
cpp
#include <vector>
int main() {
std::vector<int> values{1, 2, 3};
int& first = values[0];
values.push_back(4);
values.push_back(5);
first = 10; // 可能危险
}如果 push_back 导致 vector 重新分配缓冲区,旧元素会被搬到新位置,旧缓冲区释放。
first 仍然是一个引用,但它可能引用旧缓冲区里的位置。
所以对象生命周期还要和容器的存储变化一起看。
6. moved-from 对象仍然活着
移动语义容易造成另一个误解:
cpp
#include <string>
#include <utility>
std::string s = "hello";
std::string t = std::move(s);移动后,s 没有死。
s 仍然是一个有效对象。
但它的值处于“有效但未指定”的状态。
你可以:
cpp
s = "new value";不要依赖:
cpp
if (s.empty()) { }有些实现中它可能为空,但标准不要求你依赖这个细节。
这里说明:
text
生命周期结束
对象不能再按原类型使用
moved-from
对象仍然活着,但资源可能已被转移7. 本篇总结
生命周期判断顺序:
text
对象在哪里被构造?
谁拥有它?
哪个作用域或 owner 会结束它?
我手里的指针、引用、view 是否可能活得更久?
容器是否可能搬走它的存储?
移动后对象是否仍然有效?C++ 内存管理最核心的能力之一,就是不被“地址还在”迷惑。
真正要问的是:
text
这个地址上是否仍然有一个活着的、类型正确的对象?