首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >智能指针深度解析:C++内存管理的神奇利器

智能指针深度解析:C++内存管理的神奇利器

作者头像
禁默
发布2025-12-20 18:50:02
发布2025-12-20 18:50:02
3430
举报

1.智能指针的引入

观察下列程序,正常情况下,程序new的对象我们能正常释放,但是当抛异常出现后,后⾯的delete没有得到执行,所以内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出(将注释部分取消即可)。

代码语言:javascript
复制
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++引入了智能指针这位重量级人物。

2.RAII与智能指针的设计思路

在C++编程中,资源管理 是一个至关重要的问题,尤其是涉及动态内存分配、文件操作、网络连接和线程同步等场景时。如果资源没有被正确释放,就会导致 内存泄漏资源占用过长 的问题。为了解决这个问题,RAII(Resource Acquisition Is Initialization,资源获取即初始化) 设计思想应运而生,并成为 智能指针 设计的核心理念之一。


2.1 RAII(资源获取即初始化)

2.1.1 RAII 的核心概念

RAII 是一种管理资源的 C++ 编程思想,其核心原则是 利用对象的生命周期来管理资源的申请与释放,确保资源不会被错误地释放或泄露。RAII 主要包含以下几个关键点:

  1. 在对象构造时获取资源(即资源的获取和初始化绑定在一起)。
  2. 在对象析构时释放资源(当 RAII 对象离开作用域时,资源会被自动释放)。
  3. 资源在对象生命周期内始终保持有效,不会因为异常或程序流程问题导致资源泄漏。
2.1.2 RAII 的典型应用

RAII 主要用于管理需要手动释放的资源,如:

  • 动态内存管理(new/delete, malloc/free)
  • 文件操作(文件打开/关闭)
  • 互斥锁(lock/unlock)
  • 网络连接(连接/断开)
2.1.3 代码示例:RAII 管理文件句柄
代码语言:javascript
复制
#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 也能保证文件被正确关闭,避免资源泄漏。
  • 通过 RAII 方式,不需要手动 close() 文件,降低了出错的可能性。

3.1 智能指针的设计思路

C++ 的 智能指针(Smart Pointer)RAII 思想的典型应用,用于管理 动态分配的内存,避免手动 new/delete 可能导致的内存泄漏。

3.1.1 智能指针的额外需求

相比于 RAII 的一般资源管理,智能指针除了需要 在析构时释放资源 之外,还需要:

  • 模仿原生指针的行为,即可以像指针一样访问和操作对象。
  • 提供安全的引用计数(shared_ptr),支持多个智能指针共享同一块内存。
  • 支持独占管理(unique_ptr),避免多个指针同时管理同一块内存。
3.1.2 智能指针的核心机制

智能指针类 通过重载运算符 来模拟原生指针的行为:

  1. operator* 允许解引用智能指针,访问内部对象。
  2. operator-> 允许使用 ptr->成员 语法访问内部对象。
  3. 构造函数 负责初始化并绑定资源。
  4. 析构函数 负责在合适的时机释放资源。
3.1.3 代码示例:简单实现一个智能指针
代码语言:javascript
复制
#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 带来的错误。

接下来我们对引入的例子进行修改(这里也是简单实现一个智能指针)

代码语言:javascript
复制
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;
}

3.C++ 标准库智能指针的使用

在 C++ 标准库中,智能指针位于 <memory> 头文件中,因此包含 <memory> 头文件后就可以使用智能指针。智能指针的设计目标是自动管理动态分配的资源,避免手动 new/delete 可能导致的内存泄漏和悬空指针问题。

除了 weak_ptr 之外,其他标准库智能指针都符合 RAII(资源获取即初始化) 原则,并且支持像原生指针一样访问资源。不同类型的智能指针,主要的区别在于拷贝语义的处理方式。


1. auto_ptr(C++98,已废弃)
  • auto_ptrC++98 设计的智能指针,其拷贝行为存在严重问题:
    • 在拷贝时,它会转移资源的管理权,导致原来的 auto_ptr 变成 悬空指针,容易引发 访问非法内存 的错误。
    • 由于这个设计缺陷,C++11 引入新的智能指针后,强烈建议不要使用 auto_ptr,甚至在 C++17 被移除
  • 许多公司在 C++11 之前,就已经明确禁止使用 auto_ptr 了。

2. unique_ptr(C++11 引入)
  • unique_ptr 代表 独占所有权,即:
    • 不支持拷贝,只允许移动std::move())。
    • 适用于 不需要拷贝 的场景,资源只能被一个对象管理。
    • 离开作用域时,自动释放资源

示例:

代码语言:javascript
复制
#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; // 资源自动释放
}

3. shared_ptr(C++11 引入)
  • shared_ptr 代表 共享所有权,即:
    • 支持拷贝和移动,多个 shared_ptr 可以共享同一个资源
    • 底层使用引用计数 来管理资源,当最后一个 shared_ptr 释放时,资源才被释放
    • 适用于 需要多个对象共享同一资源 的场景。

示例:

代码语言:javascript
复制
#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 都析构,资源才会释放
}
代码语言:javascript
复制
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;
}

4. weak_ptr(C++11 引入)
  • weak_ptr 代表 弱引用,主要用于解决 shared_ptr 循环引用的问题。
  • weak_ptr 不会影响引用计数,所以不能直接访问资源,必须通过 lock() 方法转换为 shared_ptr 才能使用。

示例:

代码语言:javascript
复制
#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;
}

5. 智能指针的删除器
  • 智能指针默认使用 delete 释放资源,因此不能直接用于管理非 new 分配的资源,否则会导致 delete 释放非法内存
  • unique_ptrshared_ptr 支持自定义删除器,即在构造时传入一个可调用对象(函数、lambda、仿函数),来指定资源释放方式。
  • 例如 new[] 需要 delete[] 释放,因此 unique_ptrshared_ptr特化了数组版本
代码语言:javascript
复制
//特化版本
unique_ptr<Date[]> p1(new Date[5]);
shared_ptr<Date[]> p2(new Date[5]);
代码语言:javascript
复制
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;
}

6. make_shared(推荐使用)
  • shared_ptr 除了可以直接用 new 资源构造,还可以使用 make_shared(),这样更高效
    • make_shared() 直接在 shared_ptr 的引用计数控制块中分配资源,减少一次 new 开销,提高性能。
    • make_shared() 更安全,避免 shared_ptr(new T()) 可能导致的内存泄漏(如果 new T() 之后 shared_ptr 构造失败,可能导致 T 资源泄漏)。

示例:

代码语言:javascript
复制
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp = std::make_shared<int>(42); // 直接构造对象
    std::cout << "值:" << *sp << std::endl;
    return 0;
}

7. 智能指针的 bool 转换

shared_ptrunique_ptr 都支持 operator bool,用于检查智能指针是否为空

代码语言:javascript
复制
std::unique_ptr<int> up;
if (up) { /* 不为空 */ } else { /* 为空 */ }

这个特性可以直接用于 if 语句,简化空指针判断。


8. 防止隐式转换

shared_ptrunique_ptr 的构造函数都使用 explicit 修饰,防止普通指针隐式转换为智能指针

代码语言:javascript
复制
void func(std::unique_ptr<int> ptr) {}

int main() {
    // func(new int(10)); // ❌ 错误,不能隐式转换
    func(std::make_unique<int>(10)); // ✅ 正确,必须显式转换
    return 0;
}

总结
  1. auto_ptr(已废弃):拷贝会导致原指针悬空,不安全。
  2. unique_ptr(推荐)独占所有权,不支持拷贝,支持移动,适用于不需要共享资源的情况。
  3. shared_ptr(推荐)共享所有权,支持拷贝和移动,底层使用引用计数
  4. weak_ptr弱引用,用于解决 shared_ptr循环引用 问题。
  5. 推荐使用 make_shared(),更安全更高效
  6. 支持自定义删除器,适用于 new[] 和非 new 资源

智能指针让 C++ 资源管理更加 自动化、安全、高效,是现代 C++ 编程的最佳实践之一! 🚀

结束语

本节内容就到此结束了,下节内容我们将进一步讲解智能指针的原理,以及对部分智能指针进行分手撕,对shared_ptr会进行进一步的讲解。 

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.智能指针的引入
  • 2.RAII与智能指针的设计思路
    • 2.1 RAII(资源获取即初始化)
      • 2.1.1 RAII 的核心概念
      • 2.1.2 RAII 的典型应用
      • 2.1.3 代码示例:RAII 管理文件句柄
    • 3.1 智能指针的设计思路
      • 3.1.1 智能指针的额外需求
      • 3.1.2 智能指针的核心机制
      • 3.1.3 代码示例:简单实现一个智能指针
  • 3.C++ 标准库智能指针的使用
    • 1. auto_ptr(C++98,已废弃)
    • 2. unique_ptr(C++11 引入)
    • 3. shared_ptr(C++11 引入)
    • 4. weak_ptr(C++11 引入)
    • 5. 智能指针的删除器
    • 6. make_shared(推荐使用)
    • 7. 智能指针的 bool 转换
    • 8. 防止隐式转换
    • 总结
  • 结束语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档