文章目录
- **1. 内存管理**
- - **存储对象**:
- - **存储指针**:
- **2. 生命周期控制**
- - **存储对象**:
- - **存储指针**:
- **3. 性能差异**
- - **存储对象**:
- - **存储指针**:
- **4. 使用场景**
- - **选择存储对象的情况**:
- - **选择存储指针的情况**:
- **5. 注意事项**
- - **存储指针的风险**:
- - **推荐实践**:
- - 使用智能指针(如 `std::unique_ptr`、`std::shared_ptr`)替代原始指针,自动管理内存:
- **6. 总结对比表**
- **7. 示例代码**
- - **存储对象**:
- - **存储指针**:
在 C++ 中,
std::vector
容器存储对象(如
int
、自定义类实例)和指针(如
int*
、
MyClass*
)时,存在显著差异,主要体现在
内存管理、生命周期控制、性能开销 和
使用场景 上。以下是详细对比:
1. 内存管理
- 存储对象:
vector
管理对象的完整生命周期。- 当
vector
被销毁或调用clear()
时,所有对象会自动调用析构函数,内存被释放。 - 示例:
std::vector<MyClass> vec; vec.push_back(MyClass()); // 存储对象 vec.clear(); // 自动释放所有对象内存
- 存储指针:
vector
仅管理指针本身的生命周期,不负责释放指针指向的对象。- 需要手动遍历
vector
并delete
每个指针指向的对象,否则会导致内存泄漏。 - 示例:
std::vector<MyClass*> vec; vec.push_back(new MyClass()); // 存储指针 for (auto* ptr : vec) delete ptr; // 手动释放内存 vec.clear(); // 仅释放指针本身
2. 生命周期控制
- 存储对象:
vector
的扩容(如push_back
超过容量时)会复制或移动对象到新内存区域,原对象被析构。- 示例:
std::vector<std::string> vec; vec.push_back("hello"); // 如果扩容,"hello" 会被复制到新内存
- 存储指针:
vector
扩容时仅复制指针(地址),不会影响指针指向的对象。- 指针指向的对象生命周期需独立管理,与
vector
无关。 - 示例:
std::vector<MyClass*> vec; MyClass* obj = new MyClass(); vec.push_back(obj); // 存储指针 // 即使 vec 扩容,obj 的内存地址不会改变
3. 性能差异
- 存储对象:
- 优点:直接存储对象,访问效率高(连续内存,缓存友好)。
- 缺点:对象较大时,频繁扩容可能导致大量复制开销(需调用拷贝构造函数)。
- 适合场景:对象较小或频繁访问,且不需要动态分配内存。
- 存储指针:
- 优点:避免对象复制开销(仅复制指针地址)。
- 缺点:需要手动管理内存(
new
/delete
),且指针访问可能因内存不连续导致缓存效率低。 - 适合场景:对象较大或需要共享所有权(如多
vector
共同管理同一对象)。
4. 使用场景
- 选择存储对象的情况:
- 对象生命周期与
vector
绑定,无需共享。 - 需要频繁访问或修改对象,且对象较小。
- 示例:存储基本类型(
int
、float
)或小型结构体。
- 选择存储指针的情况:
- 对象需要动态分配(如通过
new
创建)。 - 需要共享对象所有权(多个
vector
或其他容器指向同一对象)。 - 对象较大,复制成本高。
- 示例:存储多态对象(基类指针指向派生类实例)。
5. 注意事项
- 存储指针的风险:
- 内存泄漏:忘记
delete
指针会导致内存泄漏。 - 悬空指针:若
vector
管理的指针指向的对象被提前释放,后续访问会导致未定义行为。 - 重复释放:如知识库 [1] 提到的
double free
错误(例如在类析构函数中调用vector<T>().swap()
释放指针)。
- 推荐实践:
- 使用智能指针(如 std::unique_ptr
、std::shared_ptr
)替代原始指针,自动管理内存:
std::vector<std::unique_ptr<MyClass>> vec;
vec.push_back(std::make_unique<MyClass>());
// 不需要手动 delete,vec 析构时自动释放
6. 总结对比表
特性 | 存储对象 | 存储指针 |
---|---|---|
内存管理 | 自动释放 | 需手动 delete |
生命周期绑定 | 与 vector 绑定 | 独立于 vector |
扩容开销 | 复制对象(调用拷贝/移动构造函数) | 仅复制指针地址 |
访问效率 | 高(连续内存) | 低(间接访问) |
适用场景 | 对象小、生命周期短 | 对象大、需要共享所有权或动态分配 |
风险 | 无(除非自定义析构逻辑) | 内存泄漏、悬空指针、重复释放 |
7. 示例代码
- 存储对象:
#include <vector>
struct MyClass {
MyClass() { printf("Constructor\n"); }
~MyClass() { printf("Destructor\n"); }
};
int main() {
std::vector<MyClass> vec;
vec.push_back(MyClass()); // 对象被复制到 vector 中
vec.clear(); // 自动调用析构函数
}
- 存储指针:
#include <vector>
struct MyClass {
MyClass() { printf("Constructor\n"); }
~MyClass() { printf("Destructor\n"); }
};
int main() {
std::vector<MyClass*> vec;
vec.push_back(new MyClass()); // 手动分配内存
for (auto* ptr : vec) delete ptr; // 必须手动释放
vec.clear();
}
通过以上对比,可以根据具体需求选择存储对象或指针。在现代 C++ 中,优先推荐使用智能指针来避免手动内存管理的风险。