【C++11】智能指针
【C++11】智能指针
智能指针的使用及原理
RALL
RALL:利用对象生命周期来控制程序资源,在对象构造时获取资源,在对象析构的时释放资源
即: 资源交给对象管理,对象生命周期内,资源有效,对象生命周期到了,释放资源
利:
1.不需要显式地释放资源
2.对象所需的资源在其生命期内始终保持有效
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{
}
~SmartPtr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
int main()
{
SmartPtr<int> sp1(new int);
return 0;
}
智能指针的原理
- RAII特性
- 重载operator*和opertaor->,具有像指针一样的行为。
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
C++11 引入智能指针的根本目的
解决手动管理动态内存时的内存泄漏、野指针、重复释放等问题。它的核心思想是RAII(资源获取即初始化):将动态分配的内存资源绑定到智能指针的生命周期上,当智能指针对象超出作用域(如函数执行完毕、异常抛出)时,其析构函数会自动释放所管理的内存,无需手动调用delete
智能指针本质是封装了裸指针的模板类
std::auto_ptr(C++98)
auto_ptr的实现原理:管理权转移,这里其实就是通过将赋值对象置nullptr或析构来达到目的,但是会带来”悬空“问题
悬空问题:其实就是赋值对象置空或析构了,后续如果对其操作会带来的问题
简化模拟实现wch::auto_ptr:
#include <iostream>
#include <memory> // 包含 auto_ptr 的头文件
using namespace std;
// C++98 管理权转移 auto_ptr
namespace wch
{
template <class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
: _ptr(ptr)
{
}
auto_ptr(auto_ptr<T>& sp)
: _ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
int main()
{
wch::auto_ptr<int> sp1(new int);
wch::auto_ptr<int> sp2(sp1); // 管理权转移
// sp1悬空
*sp2 = 10;
cout << *sp2 << endl;
cout << *sp1 << endl;
return 0;
}
std::unique_ptr(C++11)
核心特性:独占所有权,同一时间只能有一个unique_ptr指向同一个对象,不允许拷贝(C++11 中),仅支持移动(std::move)。
适用场景:明确资源独占的场景(如局部动态对象、函数返回动态对象)
#include <iostream>
#include <memory>
class Test {
public:
Test(int v) : val(v) { std::cout << "Test构造: " << val << std::endl; }
~Test() { std::cout << "Test析构: " << val << std::endl; }
int val;
};
int main() {
// 1. 基本使用:独占指向Test对象
std::unique_ptr<Test> ptr1(new Test(10));
std::cout << ptr1->val << std::endl; // 访问对象成员
// 2. 不允许拷贝(编译报错)
// std::unique_ptr<Test> ptr2 = ptr1;
// 3. 支持移动:所有权转移
std::unique_ptr<Test> ptr3 = std::move(ptr1);
if (!ptr1) { // ptr1已失去所有权,变为空
std::cout << "ptr1为空" << std::endl;
}
// 4. 手动释放资源(一般不需要,析构会自动释放)
ptr3.reset();
return 0;
}
//Test构造: 10
//10
//ptr1为空
//Test析构: 10
unique_ptr的实现原理:简单粗暴的防拷贝
// 原理:简单粗暴 -- 防拷贝
namespace wch
{
template <class T>
class unique_ptr
{
public:
unique_ptr(T *ptr)
: _ptr(ptr)
{
}
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T &operator*()
{
return *_ptr;
}
T *operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr<T> &sp) = delete;
unique_ptr<T> &operator=(const unique_ptr<T> &sp) = delete;
private:
T *_ptr;
};
}
std::shared_ptr(C++11)
核心特性:共享所有权,多个shared_ptr可以指向同一个对象,内部通过引用计数管理:每新增一个指向该对象的shared_ptr,引用计数 + 1;每销毁一个,引用计数 - 1;当引用计数为 0 时,自动释放对象内存。
注意点:避免循环引用(如 A 的shared_ptr指向 B,B 的shared_ptr指向 A),会导致引用计数无法归 0,内存泄漏。
shared_ptr的原理:通过引用计数的方式来实现多个shared_ptr对象之间共享资源
- shared_ptr在其内部,给每个资源(可以说是new出来的)都维护了着一份计数,用来记录该份资源被几个对象共享。
//不可静态计数器:静态计数器属于类级别而非对象级别,会导致所有智能指针对象共享同一个计数,引用计数会相互干扰 - 指向该资源空间的指针(对象)被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
// shared_ptr智能指针是线程安全的吗?
#是的,引用计数的加减是加锁保护的。但是指向资源不是线程安全的
//智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
#指向堆上资源的线程安全问题是访问的人处理的,智能指针不管,也管不了
// 引用计数的线程安全问题,是智能指针要处理的
简单模拟:
namespace wch
{
template <class T>
class shared_ptr
{
public:
shared_ptr(T *ptr = nullptr)
: _ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex)
{
}
shared_ptr(const shared_ptr<T> &sp)
: _ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx)
{
AddRef();
}
//计数器操作需加锁
void Release()
{
_pmtx->lock();
bool flag = false;
if (--(*_pRefCount) == 0 && _ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pRefCount;
flag = true;
}
_pmtx->unlock();
if (flag == true)
{
delete _pmtx;
}
}
//计数器操作需加锁
void AddRef()
{
_pmtx->lock();
++(*_pRefCount);
_pmtx->unlock();
}
shared_ptr<T> &operator=(const shared_ptr<T> &sp)
{
//必需判断是否自己给自己赋值
// if (this != &sp)
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pmtx = sp._pmtx;
AddRef();
}
return *this;
}
int use_count()
{
return *_pRefCount;
}
~shared_ptr()
{
Release();
}
// 像指针一样使用
T &operator*()
{
return *_ptr;
}
T *operator->()
{
return _ptr;
}
T *get() const
{
return _ptr;
}
private:
T *_ptr;
int *_pRefCount;
mutex *_pmtx;
};
}
std::shared_ptr的循环引用
触发场景:创建节点内有同节点类项的next和prev指针,且两个节点内的next,prev指向如图所示,(类似双链表)
解决方式:prev和next类型改为weak_ptr
- weak_ptr不是RAII智能指针,专门用来解决shared_ptr循环引用问题
- weak_ptr不增加引用计数,可以访问资源,不参与资源释放的管理

weak_ptr:简化版本
// 简化版本的weak_ptr实现
namespace wch
{
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;
};
}
std::weak_ptr(弱引用智能指针)
核心特性:配合shared_ptr使用,是对对象的弱引用,不增加引用计数,也不拥有对象的所有权。主要用于解决shared_ptr的循环引用问题。
解决循环引用
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::weak_ptr<B> b_ptr; // 用weak_ptr而非shared_ptr
~A() { std::cout << "A析构" << std::endl; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // 用weak_ptr而非shared_ptr
~B() { std::cout << "B析构" << std::endl; }
};
int main() {
std::shared_ptr<A> a(new A);
std::shared_ptr<B> b(new B);
a->b_ptr = b;
b->a_ptr = a;
// 引用计数仍为1,析构时会正常释放
std::cout << "a的引用计数: " << a.use_count() << std::endl; // 1
std::cout << "b的引用计数: " << b.use_count() << std::endl; // 1
return 0;
}
非new出来的对象如何通过智能指针管理呢?-> by 删除器
shared_ptr设计了一个删除器来解决这个问题
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>
using namespace std;
// 仿函数的删除器
template<class T>
struct FreeFunc {
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc {
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << this;
cout << " ~A()" << endl;
}
// private:
int _a;
};
int main()
{
FreeFunc<int> freeFunc;
std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
std::shared_ptr<A> sp4(new A[10], [](A* p) {delete[] p; });
//std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p)
{fclose(p); });
return 0;
}
C++11和boost中智能指针的关系
- C++ 98 中产生了第一个智能指针auto_ptr.
- boost库给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
- C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost
的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
使用智能指针的最佳实践
- 优先使用std::make_unique(C++14)/std::make_shared创建智能指针,而非直接new:
避免内存泄漏(如shared_ptr(new int)在异常时可能泄漏);
make_shared会一次性分配对象内存和引用计数内存,效率更高。
// 推荐写法
auto ptr = std::make_shared<int>(10);
auto u_ptr = std::make_unique<Test>(20);
- 不要用同一个裸指针初始化多个unique_ptr(会导致重复释放);
- 不要将智能指针管理的裸指针手动delete(会导致析构时重复释放);
- weak_ptr不能直接访问对象,必须通过lock()转为shared_ptr后再访问。
std::make_shared 的底层原理
std::make_shared 是 C++11 提供的一个用于创建 shared_ptr 的工厂函数,它的核心设计目标是更高效、更安全地分配和管理对象。
一、核心实现原理
- 一次内存分配,效率更高
- 直接使用 new 和 shared_ptr 构造函数时,会进行两次独立的内存分配:一次为对象本身,另一次为 shared_ptr 的控制块。
- make_shared 则将对象和控制块的内存分配合并为一次,它在堆上申请一块连续的内存,前半部分存放控制块,后半部分存放对象。
- 这减少了内存分配的开销和内存碎片,并且让缓存的局部性更好
- 原子初始化,异常安全
- make_shared 在分配完连续内存后,会在原地构造对象,并初始化控制块中的引用计数为 1。
- 这个过程是原子性的,避免了在“分配对象”和“分配控制块”之间发生异常时,导致内存泄漏的风险
- 例如, shared_ptr(new T()) 中,如果 new T() 成功但 shared_ptr 构造函数抛出异常,已分配的 T 对象内存就会泄漏,而 make_shared 不存在这个问题
- 支持完美转发
- make_shared 支持完美转发(Perfect Forwarding),可以将你传入的任意参数原封不动地传递给对象的构造函数
//普通方式(两次分配)
// 1. 分配对象内存
int* raw_ptr = new int(42);
// 2. 分配控制块内存,并创建 shared_ptr
std::shared_ptr<int> p(raw_ptr);
//使用 make_shared (一次分配)
// 一次分配连续内存,包含控制块和对象
auto p = std::make_shared<int>(42);
std::make_shared 潜在的缺点
无法自定义删除器: make_shared 不支持传入自定义的删除器( deleter
更多推荐



所有评论(0)