C++11智能指针与 RAII
C++ 中资源管理的核心思想:利用对象生命周期自动释放资源,避免内存泄漏、双重释放等问题。 本部分涵盖:RAII、简易智能指针、auto_ptr、unique_ptr、shared_ptr 及循环引用、weak_ptr 原理与实现。
1. 传统 new/delete 的缺陷与 RAII 思想
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void f()
{
pair<string, string>* p1 = new pair<string, string>;
pair<string, string>* p2 = new pair<string, string>;
pair<string, string>* p3 = new pair<string, string>;
pair<string, string>* p4 = new pair<string, string>;
try
{
div();
}
catch (...)
{
delete p1;
cout << "delete:" << p1 << endl;
throw;
}
div();
delete p1;
//如果 div() 抛异常,catch 里面 delete p1 一次,重新抛出后 函数末尾的 delete p1 又执行一次 → 未定义行为(双重释放)
//若div()正常,但p2p3p4无法得到释放,存在内存泄漏
}
问题场景:当异常抛出时,函数中手工 new 的资源很容易忘记释放,导致泄漏或双重释放。
new/delete的缺陷
1.必须在所有执行路径上都配对 new 和 delete
2.异常会打乱正常控制流
3.指针没有所有权语义,p1仅是一个地址,无法表达谁负责释放,也不能在离开作用域时自动销毁
4.可维护性差
2. 最简 RAII 智能指针
RAII(Resource Acquisition Is Initialization,资源获取即初始化) 是 C++ 中一种重要的资源管理编程范式,利用对象生命周期来控制程序资源
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
void f()
{
pair<string, string>* p1 = new pair<string, string>;
// ... 可能抛出异常的代码 div();
SmartPtr sp1(p1); // 资源托管给智能指针,离开作用域自动释放
SmartPtr<pair<string, string>> sp2(new pair<string, string>("hello", "world"));
SmartPtr<string> sp3(new string("hello world"));
std::cout << *sp3 << endl;
std::cout << sp2->first << " " << sp2->second << endl;
}
-
RAII(Resource Acquisition Is Initialization):资源获取即初始化,将资源(如堆内存)的生命周期绑定到对象的生命周期。对象构造时获取资源,析构时自动释放。
-
上面的
SmartPtr通过重载operator*和operator->模拟指针行为。 -
缺陷:不支持拷贝(拷贝会导致同一指针被两次
delete)。
3. auto_ptr 的历史教训
template <typename T>
class AutoPtr
{
T* ptr;
public:
explicit AutoPtr(T* p = nullptr) : ptr(p) {}
~AutoPtr() { delete ptr; }
// 拷贝构造:转移所有权(可怕的“假拷贝”)
AutoPtr(AutoPtr& other)
: ptr(other.ptr)
{
other.ptr = nullptr; // 源指针被置空
}
// 拷贝赋值运算符:转移所有权
AutoPtr& operator=(AutoPtr& other)
{
if (this != &other)
{
delete ptr; // 释放自己原来的资源
ptr = other.ptr; // 接管对方的资源
other.ptr = nullptr;// 源指针置空
}
return *this;
}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
};
void func(AutoPtr<int> p) {} // 按值传参会发生所有权转移
void test_2()
{
AutoPtr<int> sp1(new int(5));
AutoPtr<int> sp2 = sp1; // 看似拷贝,实则sp1 被置空,sp2 接管资源
// *sp1 = 10; // 崩溃!sp1 已经是 nullptr
func(sp2); // sp2所有权转移到参数 p,函数返回后 p 析构,资源释放
// *sp2 = 11; // 崩溃!sp2 悬空
AutoPtr<int> arr(new int[10]); // 析构时调用 delete,而非 delete[],未定义行为
}
-
C++98 引入的
auto_ptr试图用所有权转移解决拷贝问题,但其“拷贝即转移”的行为违反直觉,导致悬空指针和崩溃。 -
不能用于 STL 容器,且不支持数组。
-
C++11 废弃
auto_ptr,引入独占所有权的unique_ptr和共享所有权的shared_ptr。
4. unique_ptr —— 独占所有权
unique_ptr禁止拷贝,shared_ptr支持拷贝构造
void test_3()
{
unique_ptr<string> up1(new string("unique_ptr_1"));
unique_ptr<string> up2(new string("unique_ptr_2"));
cout << "up1:" << *up1 << endl;
// unique_ptr<string> up3(up2); // 错误:禁止拷贝
unique_ptr<string> up3 = move(up1); // 显式移动,up1 变空
cout << "up2:" << *up2 << " up3:" << *up3 << endl;
shared_ptr<string> sp1(new string("shared_ptr_1"));
shared_ptr<string> sp2(new string("shared_ptr_2"));
shared_ptr<string> sp3(sp2);
cout << "sp1:" << *sp1 << " sp2:" << *sp2 << " sp3:" << *sp3 << endl;
}
自定义实现(简化版)
template<class T>
class Unique_Ptr
{
public:
Unique_Ptr(T* ptr) : _ptr(ptr) {}
~Unique_Ptr() { cout << "delete:" << _ptr << endl; delete _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
Unique_Ptr(Unique_Ptr<T>& ap) = delete; // 禁止拷贝构造
Unique_Ptr<T>& operator=(Unique_Ptr<T>& ap) = delete; // 禁止拷贝赋值
private:
T* _ptr;
};
-
unique_ptr独占资源所有权,不可拷贝只可移动,语义清晰安全。 -
通过
= delete明确禁止拷贝,同时支持移动构造/移动赋值(示例未实现移动,真实unique_ptr支持move转移所有权)。 -
相比于
auto_ptr,所有权转移必须显式使用std::move,不会在不知不觉中发生悬空。
5. shared_ptr 与引用计数
template<class T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
~Shared_Ptr()
{
if (--(*_pcount) == 0)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pcount;
}
}
explicit operator bool() const { return _ptr != nullptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
Shared_Ptr(const Shared_Ptr<T>& sp)
:_ptr(sp._ptr), _pcount(sp._pcount)
{
++(*_pcount);
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp)
{
if (_ptr == sp._ptr) return *this;
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
int use_count() const { return *_pcount; }
T* get() const { return _ptr; }
int* get_pcount() const { return _pcount; }
private:
T* _ptr;
int* _pcount; // 引用计数,所有共享者共享同一块计数内存
};
-
shared_ptr通过引用计数实现共享所有权:每多一个对象共享,计数加一;析构或赋值释放时计数减一,减到 0 时释放资源。 -
内部使用了堆上的
int来存储计数,确保所有副本可见同一计数。 -
拷贝构造和赋值操作需要正确处理计数的增减。
-
提供了
use_count()获取当前引用数,get()获取裸指针。
6. weak_ptr 与循环引用
循环引用问题:当两个对象互相持有对方的 shared_ptr 时,引用计数永远不会归零,导致内存泄漏。
struct Node
{
A _val;
// Node* _next; // 使用裸指针无法自动释放
// Node* _prev;
Weak_Ptr<Node> _next; // 使用 weak_ptr 打破循环
Weak_Ptr<Node> _prev;
};
自定义 Weak_Ptr(简化演示)
template<class T>
class Weak_Ptr
{
public:
Weak_Ptr() : _ptr(nullptr) {}
Weak_Ptr(const Shared_Ptr<T>& sp) : _ptr(sp.get()) {}
Weak_Ptr<T>& operator=(const Shared_Ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
-
weak_ptr是一种弱引用,它指向由shared_ptr管理的对象,但不增加引用计数。 -
主要用途:解决
shared_ptr的循环引用问题。比如双向链表中,节点间可以使用shared_ptr指向下一个,但指向前一个应使用weak_ptr,否则形成环,导致永远不会释放。 -
上述例子里,
Weak_Ptr仅保存裸指针,未与Shared_Ptr的引用计数联动(完美的weak_ptr需能检测资源是否已释放,标准库的weak_ptr可通过lock()返回一个有效的shared_ptr或空指针)。 -
不参与资源释放管理:
weak_ptr不会在析构时delete对象,也不阻止对象被销毁。
7. 综合测试:所有权转移与循环引用
class A
{
public:
A(int a = 0) : _a(a) { cout << "A(int a = 0)" << endl; }
~A() { cout << this; cout << " ~A()" << endl; }
private:
int _a;
};
void test_4()
{
cout << "---------- Unique_Ptr独占所有权 ----------" << endl;
Unique_Ptr<string> up1(new string("Unique_Ptr_1"));
Unique_Ptr<string> up2(new string("Unique_Ptr_2"));
cout << "UP1:" << *up1 << " UP2:" << *up2 << endl;
// up1 = up2; // ❌ 拷贝赋值被 delete
cout << "\n--------- Shared_Ptr + Weak_Ptr共享与观察 ----------" << endl;
Shared_Ptr<string> sp1(new string("Shared_Ptr_1"));
cout << "SP1: " << *sp1 << " use_count: " << sp1.use_count() << endl; // 1
Weak_Ptr<string> wp1(sp1); // 观察 sp1
cout << "wp1: " << *wp1 << endl;
Shared_Ptr<string> sp2(sp1); // 共享,计数变 2
cout << "SP2: " << *sp2 << " use_count: " << sp1.use_count() << endl;
Weak_Ptr<string> wp2;
wp2 = sp2;
cout << "wp2: " << *wp2 << endl;
Shared_Ptr<string> sp3(new string("Shared_Ptr_2"));
cout << "SP3: " << *sp3 << " use_count: " << sp3.use_count() << endl;
sp3 = sp1; // sp3 释放原资源,改为指向 sp1 的资源,计数变 3
cout << "after sp3 = sp1, SP3: " << *sp3 << " use_count: " << sp1.use_count() << endl;
// 离开内层作用域 sp2, sp3 析构,但 sp1 仍持有,资源不会被释放
}
-
所有
Weak_Ptr对象(wp1,wp2)仅观察资源,不影响Shared_Ptr的引用计数。 -
当最后一个
Shared_Ptr被销毁时,资源被释放。此时若再用weak_ptr访问则属于野指针,标准库的weak_ptr提供expired()判断和lock()安全访问。 -
上述自定义
Weak_Ptr缺乏安全检查,仅用于演示弱引用的概念。实际项目中请使用std::weak_ptr。
总结
RAII:资源绑定对象生命周期,自动释放。
unique_ptr:独占所有权,不可拷贝,可移动。
shared_ptr:共享所有权,引用计数,注意循环引用。
weak_ptr:弱引用,不增加计数,配合shared_ptr打破循环引用。现代 C++ 应尽量避免裸 new/delete,优先使用智能指针。
更多推荐



所有评论(0)