【C++】手撕STL系列之智能指针

Posted by 卢小胖 on 2022-07-14
Estimated Reading Time 6 Minutes
Words 1.3k In Total
Viewed Times

手撕STL系列之智能指针

C++11智能指针:

  • shared_ptr: 基于引用计数实现
  • unique_ptr: 不可以移动的智能指针
  • weak_ptr: 解决shared_ptr循环引用问题,更加类似于shared_ptr的管理指针工具。

shared_ptr

shared_ptr是基于引用计数实现,可在生命周期内自动管理内存

我们先实现一个应用计数的类,用atomic实现,保证线程安全:


class SharedCount
{
public:
SharedCount() : count(1) {}

void add()
{
count++;
}

void minus()
{
count--;
}

int get() const
{
return count;
}

private:
std::atomic<int> count;

};

实现shared_ptr注意点:

  • 实现赋值构造函数和移动构造函数,
  • 重写拷贝赋值和移动赋值符号
  • 再就是shared_ptr的一些其他函数,比如use_count(),get()等方法
  • 管理引用计数,在计数==1的时候删除内存。
#include <iostream>
#include <memory>
#include <atomic>

using namespace std;


template <typename T>
class SharedPtr{

public:
//普通的构造函数
SharedPtr() : ptr(nullptr), ref_count(new SharedCount()) {}

explicit SharedPtr(T* _ptr) : ptr(_ptr), ref_count(new SharedCount()) {}

//拷贝构造函数
SharedPtr(const SharedPtr& _s_ptr)
{
this->ptr = _s_ptr.ptr;
this->ref_count = _s_ptr.ref_count;
ref_count->add();
}

SharedPtr& operator=(const SharedPtr& _s_ptr)
{
clean();
this->ptr = _s_ptr.ptr;
this->ref_count = _s_ptr.ref_count;
ref_count->add();
return *this;
}

//移动构造函数
SharedPtr(SharedPtr&& _s_ptr)
{
this->ptr = _s_ptr.ptr;
this->ref_count = _s_ptr.ref_count;
_s_ptr.ptr = nullptr;
_s_ptr.ref_count = nullptr;
}

//移动赋值
SharedPtr& operator=(SharedPtr&& _s_ptr)
{
clean();
this->ptr = _s_ptr.ptr;
this->ref_count = _s_ptr.ref_count;
_s_ptr.ptr = nullptr;
_s_ptr.ref_count = nullptr;
return *this;
}

//常用方法
T* get() const
{
return ptr;
}

T& operator* () const
{
return *ptr;
}

T* operator-> () const
{
return ptr;
}

int use_count()
{
return ref_count->get();
}

~SharedPtr(){
clean();
}

private:
T* ptr;
SharedCount* ref_count;

void clean()
{
if (ref_count)
{
ref_count->minus();
if (ref_count->get() == 0)
{
if(ptr) delete ptr;
delete ref_count;
}
}
}

};


shared_ptr之间有个强制转换static_pointer_cast:具体实现

template<typename T, typename U>
SharedPtr<T> static_pointer_cast(const SharedPtr<U>& up)
{
T* ptr = static_cast<T*>(up.get());
return SharedPtr(up,ptr);
}


//需要在SharedPtr中新增一个用来转换的构造函数:
//这里用到友元是因为ref_count是私有的,必须通过友元访问。

template<typename U>
friend class SharedPtr;

template<typename U>
SharedPtr(const SharedPtr<U>& up, T* tp)
{
this->ptr = tp;
this->ref_count = up.ref_count;
ref_count->add();
}

测试:


class A
{
public:
A() { std::cout << "A() \n"; }
~A() { std::cout << "~A() \n"; }
};

class B : public A
{
public:
B() { std::cout << "B() \n"; }
~B() { std::cout << "~B() \n"; }
};


void test_shared_ptr()
{
SharedPtr<size_t> ptr1(new size_t(110));
cout << ptr1.use_count() << endl; //1

{
SharedPtr<size_t> ptr2 = ptr1;
cout << ptr1.use_count() << endl; //2

SharedPtr<size_t> ptr3(ptr1);
cout << ptr1.use_count() << endl; //3

SharedPtr<size_t> ptr4 = std::move(ptr2);
cout << ptr4.use_count() << endl; //3
}

cout << ptr1.use_count() << endl; //1

}

void test_static_cast()
{
B* b = new B();
SharedPtr<B> ptr(b);
cout << ptr.use_count() << endl;
{
SharedPtr<A> ptr_a = static_pointer_cast<A,B>(ptr);
cout << ptr_a.use_count() << endl;
}
cout << ptr.use_count() << endl;
}

int main(){
//test_shared_ptr();
test_static_cast();
};

unique_ptr

特点:

  1. 没有引用计数
  2. 禁止拷贝赋值
#include <iostream>


template<typename T>
class unique_ptr
{
private:
T* ptr;

void clean()
{
if (ptr)
{
delete ptr;
ptr = nullptr;
}
}


public:
unique_ptr() : ptr(nullptr) {}

unique_ptr(T* ptr) : ptr(ptr) {}


unique_ptr(unique_ptr& u_ptr) = delete;
unique_ptr& operator=(unique_ptr& u_ptr) = delete;

unique_ptr(unique_ptr&& u_ptr)
{
this->ptr = u_ptr.ptr;
u_ptr.ptr = nullptr;
}

unique_ptr& operator=(unique_ptr&& u_ptr)
{
clean();
this->ptr = u_ptr.ptr;
u_ptr.ptr = nullptr;
return *this;
}

T* get() const
{
return ptr;
}

T* operator->() const
{
return ptr;
}

T& operator*() const
{
return *ptr;
}

~unique_ptr()
{
clean();
}
};

weak_ptr

weak_ptr是弱智能指针对象,它不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的智能指针。将一个weak_ptr绑定到一个shared_ptr对象,不会改变shared_ptr的引用计数。一旦最后一个所指向对象的shared_ptr被销毁,所指向的对象就会被释放,即使此时有weak_ptr指向该对象,所指向的对象依然被释放。


void checkwp(weak_ptr<string>& wp)
{
//检查wp管理的sp是否有效
if (!wp.expired())
{
//lock方法用来获取原始sp指针
auto sp = wp.lock();
std::cout << *sp << std::endl;
}
else
{
std::cout << "wp expired" << std::endl;
}
}


void test()
{
shared_ptr<string> sp1 = make_shared<string>("bug maker!");

//不同的构造方式
weak_ptr<string> wp1 = sp1;
weak_ptr<string> wp2(sp1);
weak_ptr<string> wp3 = wp2;

checkwp(wp1);

//释放sp指针, 这里只是wp1释放了sp指针,wp2和wp3还持有sp的指针
wp1.reset();
checkwp(wp1);

//wp2和wp3还持有sp的指针
checkwp(wp2);

//原始指针
std::cout << (wp2.lock() == wp3.lock() ? "true" : "false") << std::endl;
std::cout << (sp1 == wp2.lock() ? "true" : "false") << std::endl;

}

bug maker!
wp expired
bug maker!
true
true

实现weak_ptr的注意点在于:

  • 只能使用shared_ptr和weak_ptr来构造weak_ptr
  • 没有自己的数据成员,而是只提供了一些供调用的接口
  • 没有重载operator *,operator-> 函数

辅助函数:

  • reset 释放被管理对象的所有权。调用后*this不管理对象
  • swap 交换被管理对象,只能与其他weak_ptr对象交换。不调整引用计数
  • use_count() 返回管理该资源对象的shared_ptr指针对象的数量
  • expired() 检直被引用的资源对象是否已删除
  • Iock() 创建管理被引用的对象的shared_ptr,是线性安全的

参考: