观察下列程序,正常情况下,程序new的对象我们能正常释放,但是当抛异常出现后,后⾯的delete没有得到执行,所以内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出(将注释部分取消即可)。
double Divide(int a, int b)
{
// throw "Divide by zero condition!";
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void func() {
int * arr1= new int[10];
int * arr2 = new int[10];
/*try {*/
int x;
int y;
cin >> x >> y;
cout << Divide(x, y) << endl;
/*}*/
//catch (...) {
// cout << "delete []" << arr1 << endl;
// cout << "delete []" << arr2 << endl;
// delete[] arr1;
// delete[] arr2;
// throw;//异常重新抛出
//}
cout << "delete []" << arr1 << endl;
delete[] arr1;
cout << "delete []" << arr2 << endl;
delete[] arr2;
}
int main()
{
try
{
func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}

但是因为new本身也可能抛异常,两个new和Divide同时有问题呢,那我们就要套很多个try---catch语句,就很麻烦,因此C++引入了智能指针这位重量级人物。
在C++编程中,资源管理 是一个至关重要的问题,尤其是涉及动态内存分配、文件操作、网络连接和线程同步等场景时。如果资源没有被正确释放,就会导致 内存泄漏 或 资源占用过长 的问题。为了解决这个问题,RAII(Resource Acquisition Is Initialization,资源获取即初始化) 设计思想应运而生,并成为 智能指针 设计的核心理念之一。
RAII 是一种管理资源的 C++ 编程思想,其核心原则是 利用对象的生命周期来管理资源的申请与释放,确保资源不会被错误地释放或泄露。RAII 主要包含以下几个关键点:
RAII 主要用于管理需要手动释放的资源,如:
#include <iostream>
#include <fstream>
class FileGuard {
private:
std::fstream file;
public:
// 构造函数打开文件
FileGuard(const std::string& filename) {
file.open(filename, std::ios::out);
if (!file.is_open()) {
throw std::runtime_error("文件打开失败");
}
}
// 提供文件流操作
std::fstream& get() {
return file;
}
// 析构函数关闭文件
~FileGuard() {
if (file.is_open()) {
file.close();
std::cout << "文件已关闭\n";
}
}
};
int main() {
try {
FileGuard fg("example.txt");
fg.get() << "Hello, RAII!";
} catch (const std::exception& e) {
std::cerr << "异常:" << e.what() << std::endl;
}
return 0;
}代码解析:
FileGuard 类在构造时自动打开文件,在析构时自动关闭文件。main 函数中抛出异常,FileGuard 也能保证文件被正确关闭,避免资源泄漏。close() 文件,降低了出错的可能性。C++ 的 智能指针(Smart Pointer) 是 RAII 思想的典型应用,用于管理 动态分配的内存,避免手动 new/delete 可能导致的内存泄漏。
相比于 RAII 的一般资源管理,智能指针除了需要 在析构时释放资源 之外,还需要:
智能指针类 通过重载运算符 来模拟原生指针的行为:
operator* 允许解引用智能指针,访问内部对象。operator-> 允许使用 ptr->成员 语法访问内部对象。#include <iostream>
// 自定义智能指针
template<typename T>
class SmartPointer {
private:
T* _ptr;
public:
explicit SmartPointer(T* ptr = nullptr) : _ptr(ptr) {}
~SmartPointer() {
delete _ptr;
std::cout << "资源已释放\n";
}
// 重载 * 和 -> 访问对象
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
};
class Test {
public:
void show() { std::cout << "智能指针测试\n"; }
};
int main() {
SmartPointer<Test> sp(new Test());
sp->show(); // 使用 -> 访问 Test 的方法
return 0; // 离开作用域时,资源自动释放
}代码解析:
SmartPointer 通过 重载 operator* 和 operator->,使其行为类似于普通指针。new 分配的对象,析构时 自动释放 内存,避免手动 delete 带来的错误。接下来我们对引入的例子进行修改(这里也是简单实现一个智能指针)
class SmartPtr
{
public:
//模仿RAII
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout<< "delete " << _ptr << endl;
delete[] _ptr;
}
//重载运算符,模拟指针行为,方便访问资源
T* operator ->()
{
return _ptr;
}
T& operator *()
{
return *_ptr;
}
T& operator [](int index)
{
return _ptr[index];
}
private:
T* _ptr;
};
double Divide(int a, int b)
{
// throw "Divide by zero condition!";
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void func()
{
SmartPtr<int> sp1=new int[10];
SmartPtr<int> sp2=new int[10];
SmartPtr<pair<int, int> > sp3=new pair<int, int>[10];
for (int i = 0; i < 10; i++)
{
sp1[i] = sp2[i] = i;
}
for (int i = 0; i < 10; i++)
{
cout << sp1[i] << " ";
}
int x, y;
cin >> x >> y;
cout << Divide(x, y) << endl;
}
在 C++ 标准库中,智能指针位于 <memory> 头文件中,因此包含 <memory> 头文件后就可以使用智能指针。智能指针的设计目标是自动管理动态分配的资源,避免手动 new/delete 可能导致的内存泄漏和悬空指针问题。
除了 weak_ptr 之外,其他标准库智能指针都符合 RAII(资源获取即初始化) 原则,并且支持像原生指针一样访问资源。不同类型的智能指针,主要的区别在于拷贝语义的处理方式。
auto_ptr(C++98,已废弃)auto_ptr 是 C++98 设计的智能指针,其拷贝行为存在严重问题: auto_ptr 变成 悬空指针,容易引发 访问非法内存 的错误。auto_ptr,甚至在 C++17 被移除。auto_ptr 了。unique_ptr(C++11 引入)unique_ptr 代表 独占所有权,即: std::move())。示例:
#include <iostream>
#include <memory>
class Test {
public:
void show() { std::cout << "使用 unique_ptr\n"; }
};
int main() {
std::unique_ptr<Test> ptr = std::make_unique<Test>();
ptr->show();
// std::unique_ptr<Test> ptr2 = ptr; // 错误!unique_ptr 不允许拷贝
std::unique_ptr<Test> ptr2 = std::move(ptr); // 允许移动
return 0; // 资源自动释放
}shared_ptr(C++11 引入)shared_ptr 代表 共享所有权,即: shared_ptr 可以共享同一个资源。shared_ptr 释放时,资源才被释放。示例:
#include <iostream>
#include <memory>
class Test {
public:
void show() { std::cout << "使用 shared_ptr\n"; }
};
int main() {
std::shared_ptr<Test> p1 = std::make_shared<Test>(); // 推荐使用 make_shared
std::shared_ptr<Test> p2 = p1; // p1 和 p2 共享同一资源
p1->show();
std::cout << "引用计数:" << p1.use_count() << std::endl; // 输出 2
return 0; // 只有当 p1 和 p2 都析构,资源才会释放
}class Date {
public:
Date(int year=2025, int month=3, int day=15)
:_year(year), _month(month), _day(day)
{
cout << "Date()" << endl;
}
~Date() {
cout << "~Date()" << endl;
}
int _year;
int _month;
int _day;
};
#include <memory>
int main() {
//拷贝后,p1悬空了
/*auto_ptr<Date> p1(new Date(2018, 1, 1));
auto_ptr<Date> p2(p1);
unique_ptr<Date> p3(new Date(2018, 1, 1));*/
//unique_ptr<Date> p4(p3)--error;
//不支持拷贝,支持移动构造,移动后p3悬空
//unique_ptr<Date> p4(move(p3));
shared_ptr<Date> sp1(new Date);
// ⽀持拷贝
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp2);
cout << sp1.use_count() << endl;
sp1->_year++;
cout << sp1->_year << endl;
cout << sp2->_year << endl;
cout << sp3->_year << endl;
// ⽀持移动,但是移动后sp1也悬空
shared_ptr<Date> sp4(move(sp1));
cout<<sp1.get() << endl;
return 0;
}weak_ptr(C++11 引入)weak_ptr 代表 弱引用,主要用于解决 shared_ptr 循环引用的问题。weak_ptr 不会影响引用计数,所以不能直接访问资源,必须通过 lock() 方法转换为 shared_ptr 才能使用。示例:
#include <iostream>
#include <memory>
class Test {
public:
void show() { std::cout << "使用 weak_ptr\n"; }
};
int main() {
std::shared_ptr<Test> sp = std::make_shared<Test>();
std::weak_ptr<Test> wp = sp; // 不影响引用计数
if (auto ptr = wp.lock()) { // 需要转换为 shared_ptr 才能访问
ptr->show();
} else {
std::cout << "对象已释放\n";
}
return 0;
}delete 释放资源,因此不能直接用于管理非 new 分配的资源,否则会导致 delete 释放非法内存。unique_ptr 和 shared_ptr 支持自定义删除器,即在构造时传入一个可调用对象(函数、lambda、仿函数),来指定资源释放方式。new[] 需要 delete[] 释放,因此 unique_ptr 和 shared_ptr 也特化了数组版本。//特化版本
unique_ptr<Date[]> p1(new Date[5]);
shared_ptr<Date[]> p2(new Date[5]);struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
~Date()
{
cout << "~Date()" << endl;
}
};
template<class T>
class DeleteArray
{
public:
void operator()(T* ptr)
{
delete[] ptr;
}
};
template<class T>
void DeleteFunc(T* ptr) {
delete[] ptr;
}
class Fclose
{
public:
void operator()(FILE* ptr)
{
cout << "fclose:" << ptr << endl;
fclose(ptr);
}
};
int main() {
//程序崩溃
//unique_ptr<Date> up1(new Date[10]);
//shared_ptr<Date> sp1(new Date[10]);
// --------特化版本
//unique_ptr<Date[]> p1(new Date[5]);
// shared_ptr<Date[]> p2(new Date[5]);
//仿函数对象
/// unique_ptr<Date, DeleteArray<Date>> p3(new Date[10],DeleteArray<Date>());
// unique_ptr和shared_ptr支持删除器的方式有所不同
// unique_ptr是在类模板参数支持的,shared_ptr是构造函数参数支持的
// 使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调⽤
/*unique_ptr<Date, DeleteArray<Date>> p3(new Date[10]);
shared_ptr<Date> p4(new Date[10], DeleteArray<Date>());*/
//函数指针版本
//unique_ptr<Date, void(*)(Date*)> p1(new Date[3], DeleteFunc<Date>);
//shared_ptr<Date> p2(new Date[3], DeleteFunc<Date>);
//lambda版本
auto Delete = [](Date* ptr) {
delete[] ptr;
};
//decltype(Delete) 获取 lambda 的类型,并作为 unique_ptr 的删除器类型。
unique_ptr<Date, decltype(Delete)> p1(new Date[3], Delete);
shared_ptr<Date> p2(new Date[3], [](Date* ptr) {
delete[] ptr;
});
// 实现其他资源管理的删除器
shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());
shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {
cout << "fclose:" << ptr << endl;
fclose(ptr);
});
return 0;
}

make_shared(推荐使用)shared_ptr 除了可以直接用 new 资源构造,还可以使用 make_shared(),这样更高效: make_shared() 直接在 shared_ptr 的引用计数控制块中分配资源,减少一次 new 开销,提高性能。make_shared() 更安全,避免 shared_ptr(new T()) 可能导致的内存泄漏(如果 new T() 之后 shared_ptr 构造失败,可能导致 T 资源泄漏)。示例:
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(42); // 直接构造对象
std::cout << "值:" << *sp << std::endl;
return 0;
}bool 转换shared_ptr 和 unique_ptr 都支持 operator bool,用于检查智能指针是否为空:
std::unique_ptr<int> up;
if (up) { /* 不为空 */ } else { /* 为空 */ }这个特性可以直接用于 if 语句,简化空指针判断。

shared_ptr 和 unique_ptr 的构造函数都使用 explicit 修饰,防止普通指针隐式转换为智能指针:
void func(std::unique_ptr<int> ptr) {}
int main() {
// func(new int(10)); // ❌ 错误,不能隐式转换
func(std::make_unique<int>(10)); // ✅ 正确,必须显式转换
return 0;
}
auto_ptr(已废弃):拷贝会导致原指针悬空,不安全。unique_ptr(推荐):独占所有权,不支持拷贝,支持移动,适用于不需要共享资源的情况。shared_ptr(推荐):共享所有权,支持拷贝和移动,底层使用引用计数。weak_ptr:弱引用,用于解决 shared_ptr 的 循环引用 问题。make_shared(),更安全更高效。new[] 和非 new 资源。智能指针让 C++ 资源管理更加 自动化、安全、高效,是现代 C++ 编程的最佳实践之一! 🚀
本节内容就到此结束了,下节内容我们将进一步讲解智能指针的原理,以及对部分智能指针进行分手撕,对shared_ptr会进行进一步的讲解。