智能指针的使用及原理

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;
}

智能指针的原理

  1. RAII特性
  2. 重载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对象之间共享资源
在这里插入图片描述

  1. shared_ptr在其内部,给每个资源(可以说是new出来的)都维护了着一份计数,用来记录该份资源被几个对象共享。
    //不可静态计数器:静态计数器属于类级别而非对象级别,会导致所有智能指针对象共享同一个计数,引用计数会相互干扰
  2. 指向该资源空间的指针(对象)被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是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

  1. weak_ptr不是RAII智能指针,专门用来解决shared_ptr循环引用问题
  2. 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中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. boost库给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost
    的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

使用智能指针的最佳实践

  1. 优先使用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);
  1. 不要用同一个裸指针初始化多个unique_ptr(会导致重复释放);
  2. 不要将智能指针管理的裸指针手动delete(会导致析构时重复释放);
  3. weak_ptr不能直接访问对象,必须通过lock()转为shared_ptr后再访问。

std::make_shared 的底层原理

std::make_shared 是 C++11 提供的一个用于创建 shared_ptr 的工厂函数,它的核心设计目标是更高效、更安全地分配和管理对象。

一、核心实现原理

  1. 一次内存分配,效率更高
  • 直接使用 new 和 shared_ptr 构造函数时,会进行两次独立的内存分配:一次为对象本身,另一次为 shared_ptr 的控制块。
  • make_shared 则将对象和控制块的内存分配合并为一次,它在堆上申请一块连续的内存,前半部分存放控制块,后半部分存放对象。
  • 减少了内存分配的开销和内存碎片,并且让缓存的局部性更好
  1. 原子初始化,异常安全
  • make_shared 在分配完连续内存后,会在原地构造对象,并初始化控制块中的引用计数为 1。
  • 这个过程是原子性的,避免了在“分配对象”和“分配控制块”之间发生异常时,导致内存泄漏的风险
  • 例如, shared_ptr(new T()) 中,如果 new T() 成功但 shared_ptr 构造函数抛出异常,已分配的 T 对象内存就会泄漏,而 make_shared 不存在这个问题
  1. 支持完美转发
  • 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

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐