C++ 垃圾回收
引言
在编程世界里,内存管理是一个至关重要的主题。程序运行时需要分配内存来存储数据,而当不再需要这些数据时,应该释放这些内存以供其他部分使用。不当的内存管理可能导致两种主要问题:内存泄漏(分配的内存未被释放)或悬挂指针(访问已释放的内存)。
与Java、Python等具有自动垃圾回收机制的语言不同,C++通常被认为是一种需要手动管理内存的语言。但实际上,C++提供了多种方法来处理内存管理,从传统的手动分配/释放到类似垃圾回收的智能指针技术。
本文将探讨C++中的内存管理方式,特别关注与"垃圾回收"相关的概念和技术。
C++ 中的内存管理基础
在深入垃圾回收前,让我们先了解C++中内存管理的基础知识。
动态内存分配
C++允许在运行时动态分配内存,主要通过new
和delete
操作符:
// 分配单个整数的内存
int* ptr = new int;
*ptr = 10;
// 使用完后释放内存
delete ptr;
// 分配整数数组
int* arr = new int[5];
// 使用数组...
// 释放数组内存
delete[] arr;
忘记调用delete
将导致内存泄漏,程序会逐渐耗尽可用内存。
内存泄漏问题
内存泄漏是指程序分配了内存但未能释放不再使用的内存。以下是一个简单的内存泄漏示例:
void leakyFunction() {
int* number = new int(42);
// 函数结束时没有delete number
// 内存泄漏发生
}
int main() {
for(int i=0; i<1000000; i++) {
leakyFunction(); // 调用百万次,泄漏大量内存
}
return 0;
}
这种代码会随着每次函数调用不断泄漏内存,最终可能导致程序崩溃。
C++ 中的垃圾回收方法
与Java或C#等语言不同,C++标准没有内置的自动垃圾回收器。然而,C++提供了多种机制来简化内存管理并防止内存泄漏。
1. RAII (资源获取即初始化)
RAII是C++中最重要的内存管理技术之一,它将资源的生命周期与对象的生命周期绑定在一起。
#include <iostream>
#include <fstream>
void processFile(const std::string& filename) {
std::ifstream file(filename); // 资源获取
// 使用文件...
// 不需要显式关闭文件
// 当file离开作用域时,文件会自动关闭
}
当file
对象离开作用域时,其析构函数会自动调用,关闭文件并释放资源。
2. 智能指针
C++11引入了多种智能指针,它们利用RAII原则自动管理指针的生命周期:
std::unique_ptr
unique_ptr
是独占所有权的智能指针,当其被销毁时会自动删除它所指向的对象:
#include <iostream>
#include <memory>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
void use() { std::cout << "Resource being used\n"; }
};
void useResource() {
std::unique_ptr<Resource> res(new Resource());
res->use();
// 不需要手动释放,res离开作用域时会自动释放
}
int main() {
std::cout << "Starting program\n";
useResource();
std::cout << "Program continues...\n";
return 0;
}
输出:
Starting program
Resource acquired
Resource being used
Resource released
Program continues...
std::shared_ptr
shared_ptr
实现了引用计数的共享所有权模型,当最后一个指向对象的shared_ptr被销毁时,对象才会被删除:
#include <iostream>
#include <memory>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
};
int main() {
// 创建一个共享指针
std::shared_ptr<Resource> res1 = std::make_shared<Resource>();
{
// 创建另一个指向同一资源的共享指针
std::shared_ptr<Resource> res2 = res1;
std::cout << "res1 use count: " << res1.use_count() << std::endl;
std::cout << "res2 use count: " << res2.use_count() << std::endl;
} // res2离开作用域,但资源不会被释放,因为res1仍在使用它
std::cout << "After inner block, res1 use count: " << res1.use_count() << std::endl;
// 当res1离开作用域,资源会被释放
}
输出:
Resource acquired
res1 use count: 2
res2 use count: 2
After inner block, res1 use count: 1
Resource released
std::weak_ptr
weak_ptr
是一种不控制对象生命周期的智能指针,它用于打破shared_ptr
的循环引用问 题:
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
A() { std::cout << "A created\n"; }
~A() { std::cout << "A destroyed\n"; }
std::shared_ptr<B> b_ptr;
};
class B {
public:
B() { std::cout << "B created\n"; }
~B() { std::cout << "B destroyed\n"; }
std::weak_ptr<A> a_ptr; // 使用weak_ptr避免循环引用
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
return 0; // a和b正确销毁
}
如果B
类中使用std::shared_ptr<A>
而不是std::weak_ptr<A>
,就会形成循环引用,导致内存泄漏。
使用make_shared
函数通常比直接使用new
更高效,因为它只分配一次内存(同时为控制块和对象分配内存)。