实现智能指针

最后更新于 2024-07-22 401 次阅读


Unique_ptr

实现目标

  • 基于排他所有权模式:两个指针不能指向同一个资源。
  • 由于独占对象的拥有权,所以不提供拷贝构造函数和左值赋值函数重载。
  • 提供移动构造和移动赋值函数。
  • 为了实现单个对象和一组对象的管理,添加了删除器类型。
  • 在容器保存指针是安全。
  • unique_ptr具有->和*运算符重载符,因此它可以像普通指针一

实现细节

template<class _Ty, class _Dx = Default_deleter<_Ty>>
class My_unique_ptr
{
public:
 
	/*explicit  指定构造函数或转换函数 (C++11 起)或推导指引 (C++17 起)为显式,即它不能用于隐式转换和复制初始化。
	*/
	explicit My_unique_ptr(_Ty* ptr = nullptr) :m_ptr(ptr) { std::cout << "creat My_unique_ptr"; }
	~My_unique_ptr() { m_deleter(m_ptr); }
	
public:
	using pointer = _Ty*;
	using element_type = _Ty;
	using deleter_type = _Dx;
	pointer m_ptr;
	_Dx m_deleter;
};

1. 显示构造函数和析构函数

/*explicit*/My_unique_ptr(_Ty* ptr = nullptr) :m_ptr(ptr) 
{     std::cout << "creat My_unique_ptr";
}
~My_unique_ptr() 
{ 
    m_deleter(m_ptr); 
}
 
class Int
{
public:
	Int() :x_(0) { std::cout << "creat Int"; x_ += 1; }
	Int(int x) :x_(x) { std::cout << "creat Int"; }
	Int(int x, int y):x_(x) { std::cout << "creat Int"; }
	void Print()
	{
		std::cout << "Int::" << x_;
	}
public:
	int x_;
};

2. 为了满足智能指针的唯一性,不允许拷贝构造和赋值运算符的重载

My_unique_ptr(const My_unique_ptr&) = delete;
My_unique_ptr& operator=(const My_unique_ptr&) = delete;

3. 提供移动构造和移动赋值

My_unique_ptr(My_unique_ptr&& other) :m_ptr(other.m_ptr) 
{
     other.m_ptr = nullptr; 
}
	
My_unique_ptr& operator=(My_unique_ptr&& other)
	
{
    if (this != &other)
    {
        delete m_ptr;
	m_ptr = other.m_ptr;
	other.m_ptr = nullptr;
    }
    return *this;
} //xxxx

4. 实现删除器

template<class _Ty>
struct   Default_deleter
{
	void operator() (_Ty* ptr)
	{
		delete ptr;
		ptr = nullptr;
		std::cout << "destroyed";
	}
};
template<class _Ty>
struct   Default_deleter<_Ty[]>
{
	void operator ()(_Ty* ptr)
	{
		delete[]ptr;
		ptr = nullptr;
		std::cout << "destroyed ";
	}
};

Shared_ptr

实现目标

  • 它可以在多个指针之间共享同一个对象,从而避免了内存泄漏和野指针的问题。
  • shared_ptr 的实现方式通常是基于引用计数的技术,具体来说,每个 shared_ptr 对象都会维护一个引用计数器,用于记录有多少个指针指向同一个对象。当引用计数器为 0 时,表示没有任何指针指向该对象,此时会自动释放该对象的内存空间。

实现细节

#include <iostream>
#include <vector>
#include <unordered_set>

template <typename T>
class shared_ptr {
private:
    T* ptr;
    int* ref_count;
    void release() {
        if (ref_count) {
            --(*ref_count);
            if (*ref_count == 0) {
                delete ptr;
                delete ref_count;
            }
            ptr = nullptr;
            ref_count = nullptr;
        }
    }

public:
    shared_ptr() : ptr(nullptr), ref_count(nullptr) {}
    shared_ptr(T* p) : ptr(p), ref_count(new int(1)) {}
    shared_ptr(const shared_ptr& other) : ptr(other.ptr), ref_count(other.ref_count) {
        if (ref_count) {
            ++(*ref_count);
        }
    }
    ~shared_ptr() {
        release();
    }
    shared_ptr& operator=(const shared_ptr& other) {
        if (this != &other) {
            release();
            ptr = other.ptr;
            ref_count = other.ref_count;
            if (ref_count) {
                ++(*ref_count);
            }
        }
        return *this;
    }

    T* get() const {
        return ptr;
    }

    int use_count() const {
        return ref_count ? *ref_count : 0;
    }

    void reset() {
        release();
    }

    void reset(T* p) {
        release();
        ptr = p;
        ref_count = new int(1);
    }

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

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

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass constructor" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destructor" << std::endl;
    }
    void hello() {
        std::cout << "Hello, world!" << std::endl;
    }
};

int main() {
    shared_ptr<MyClass> ptr1(new MyClass());
    shared_ptr<MyClass> ptr2 = ptr1;
    std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
    std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
    ptr1->hello();
    ptr2.reset();
    std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
    std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
    return 0;
}

1. 引用计数器

在构造函数中,我们将 ref_count 初始化为 1,表示有一个指针指向该对象。在拷贝构造函数和赋值运算符中,我们将 ref_count 的值加 1,表示有一个新的指针指向该对象。在析构函数中,我们将 ref_count 的值减 1,如果 ref_count 的值为 0,则表示没有任何指针指向该对象,此时会自动释放该对象的内存空间。

2. 其它细节

在使用 shared_ptr 时,我们可以通过 get 函数获取指向对象的指针,通过 use_count 函数获取当前有多少个指针指向该对象。同时,我们还可以通过 reset 函数释放当前指针,并重新指向一个新的对象。此外,我们还可以通过 operator* 和 operator-> 运算符重载,使得 shared_ptr 对象可以像普通指针一样使用。

3. 为什么用引用计数而不用static变量呢?

static 变量可以被多个变量共享,但是这种共享方式是基于静态存储期的,而不是基于动态存储期的。具体来说,当我们定义一个 static 变量时,它会在程序运行期间一直存在,直到程序结束才会被销毁。在这个过程中,所有使用该 static 变量的变量都会共享同一个内存空间,从而实现了多变量共享的功能。

然而,在实现 shared_ptr 时,我们需要的是动态存储期的共享,即多个 shared_ptr 对象之间需要共享同一个对象的引用计数。这种共享方式是基于动态存储期的,因为 shared_ptr 对象是在程序运行期间动态创建和销毁的,而不是在程序编译期间就确定的。因此,使用 static 变量来记录引用计数无法实现动态存储期的共享,从而导致引用计数的不准确和内存泄漏的问题。