目录
[一、RAII 原理](#一、RAII 原理)
[1. std::auto_ptr](#1. std::auto_ptr)
[2. std::unique_ptr](#2. std::unique_ptr)
[3. std::shared_ptr](#3. std::shared_ptr)
[4. std::weak_ptr](#4. std::weak_ptr)
在 C++ 编程中,内存管理一直是一个重要且具有挑战性的问题。手动管理内存容易导致内存泄漏、悬空指针等问题,而 C++ 的智能指针则为我们提供了一种更安全、更高效的内存管理方式。同时,智能指针的实现也与 RAII(Resource Acquisition Is Initialization,资源获取即初始化)原理紧密相关。
一、RAII 原理
RAII 的核心思想是在构造函数中获取资源(如动态分配的内存、文件句柄等),并在析构函数中释放资源。这样可以确保资源在对象的生命周期内始终得到正确的管理,无论对象是通过正常路径还是异常路径退出作用域。
例如,下面的代码展示了一个使用 RAII 管理文件资源的类:
cpp
class FileHandler {
public:
FileHandler(const std://string& filename) : file_(std::fopen(filename.c_str(), "r")) {}
~FileHandler() {
if (file_) {
std::fclose(file_);
}
}
// 其他成员函数
private:
FILE* file_;
};
在这个例子中,FileHandler
类的构造函数打开文件,析构函数关闭文件。无论在使用文件的过程中是否发生异常,文件都会被正确地关闭。
二、智能指针的出现背景
Java 有垃圾回收机制(GC),不需要开发者手动释放内存,这大大简化了内存管理。然而,C++ 为了追求更高的效率,选择让开发者自己管理内存。但当程序中存在异常时,手动管理内存就变得有些困难,因为可能会导致资源在异常发生时没有被正确释放。为了解决这个问题,C++ 引入了智能指针。
三、不同类型的智能指针
1. std::auto_ptr
std::auto_ptr
是 C++98 引入的一种智能指针,它通过转移所有权的方式实现资源管理。当一个std::auto_ptr
对象被拷贝时,所有权会转移到新的对象,导致原对象悬空。
cpp
std::auto_ptr<int> ptr1(new int(10));
std::auto_ptr<int> ptr2 = ptr1; // ptr1 现在悬空,ptr2 拥有资源
由于std::auto_ptr
存在一些问题,在 C++11 中已被弃用。
2. std::unique_ptr
std::unique_ptr
是 C++11 引入的智能指针,它禁止拷贝,通过删除拷贝构造函数和赋值运算符来实现。这确保了资源的唯一所有权,避免了资源的重复释放。
cpp
std::unique_ptr<int> ptr1(new int(10));
// std::unique_ptr<int> ptr2 = ptr1; // 编译错误,禁止拷贝
std::unique_ptr
还支持定制删除器,可以在模板参数中传递一个函数对象,用于在资源释放时执行特定的操作
cpp
void customDeleter(int* p) {
std::cout << "自定义删除器被调用" << std::endl;
delete p;
}
std::unique_ptr<int, decltype(customDeleter)> ptr(new int(10), customDeleter);
3. std::shared_ptr
std::shared_ptr
通过引用计数实现资源的共享所有权。多个std::shared_ptr
对象可以指向同一块内存,当最后一个指向该内存的std::shared_ptr
对象被销毁时,资源才会被释放。
cpp
std::shared_ptr<int> ptr1(new int(10));
std::shared_ptr<int> ptr2 = ptr1; // 引用计数增加
std::shared_ptr
可以在函数参数中传递,方便地实现资源的共享。
cpp
void func(std::shared_ptr<int> ptr) {
// 使用资源
}
int main() {
std::shared_ptr<int> ptr(new int(10));
func(ptr);
return 0;
}
4. std::weak_ptr
std::weak_ptr
是一种弱引用智能指针,它不直接管理资源,也不增加资源的引用计数。它通常与std::shared_ptr
配合使用,用于解决循环引用导致的内存泄漏问题。
cpp
class A;
class B;
class A {
public:
std::shared_ptr<B> ptrB;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> ptrA;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->ptrB = b;
b->ptrA = a;
return 0;
}
在上面的代码中,A
和B
类相互持有对方的std::shared_ptr
,导致循环引用,即使离开main
函数的作用域,资源也不会被释放。使用std::weak_ptr
可以解决这个问题:
cpp
class A;
class B;
class A {
public:
std::shared_ptr<B> ptrB;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> ptrA;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->ptrB = b;
b->ptrA = a;
return 0;
}
现在,当离开main
函数的作用域时,资源会被正确释放。
四、总结
C++ 的智能指针是一种强大的工具,可以帮助我们更有效地管理内存资源。通过 RAII 原理,智能指针在构造函数中获取资源,在析构函数中释放资源,确保资源的正确管理。不同类型的智能指针(std::unique_ptr
、std::shared_ptr
和std::weak_ptr
)提供了不同的功能,可以根据具体的需求选择合适的智能指针。在使用智能指针时,我们需要注意避免一些常见的陷阱,如循环引用等问题,以确保程序的正确性和稳定性。
五、模拟实现shared_ptr:
shared_ptr.h
cpp
#pragma once
#include<iostream>
#include<functional>
using namespace std;
namespace xxx {
template<class T>
struct ListNode
{
public:
ListNode(const T& data = T()) : _data(data), _next(nullptr) {}
ListNode* _next = nullptr;
T _data = T();
};
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr, function<void(T*)> func = [](T* ptr)
{
delete ptr;
})
:_ptr(ptr),
_func(func)
{
*_pcount = 0;
(*_pcount)++;
}
shared_ptr(shared_ptr<T>& other)
{
_ptr = other._ptr;
_func = other._func;
_pcount = other._pcount;
(*_pcount)++;
}
shared_ptr<T>* operator=(shared_ptr<T>& other)
{
if (other._ptr == _ptr)
return this;
del();
_ptr = other._ptr;
_func = other._func;
_pcount = other._pcount;
(*_pcount)++;
return this;
}
~shared_ptr()
{
del();
}
void del()
{
if (--(*_pcount) == 0)
{
_func(_ptr);
delete _pcount;
_pcount = nullptr;
_ptr = nullptr;
}
cout << "void del()" << endl;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* _ptr;
function<void(T*)> _func;
int* _pcount = new int;
};
}
test.cpp
cpp
#include"share_ptr.h"
class Date
{
public:
Date(int year = int(), int month = int(), int day = int())
:_year(year),
_month(month),
_day(day)
{
}
~Date()
{
cout << "~Data()" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
xxx::shared_ptr<Date> sp1(new Date);
xxx::shared_ptr<Date> sp2(sp1);
xxx::shared_ptr<Date> sp3(new Date);
// 自己给自己赋值
sp3 = sp3;
sp1 = sp2;
sp1 = sp3;
sp2 = sp3;
xxx::shared_ptr<int> sp6((int*)malloc(40), [](int* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
});
return 0;
}