C++_1_手写智能指针

0 📖 前言

智能指针share_ptr<T>的使用可以规避内存泄漏情况,但是如果只是盲目的使用,不了解一下原理,内心还是不太踏实,希望本文章能够深入了解一下智能指针机理

1 📖 share_ptr<T>仿写

智能指针(shareptr<T>)有两个关键变量,而且都是指针类型:

1 引用对象的指针(类型为指针)

2 引用对象的引用次数(类型为指针)

每次智能指针对象更换对象的时候,都会先更新当前的引用次数(减一) ,然后才会接管新的传入对象更新引用计数。

对引用计数进行更新的时候

  • 新指向的对象不是智能指针,则初始化引用计数为1。
  • 新指向对象若为智能指针,则在原有的基础上加1,两个指针共享成员变量(之所以能够共享,是因为智能指针成员变量都是指针)
#include <bits/stdc++.h>
using namespace std;

template <typename T>
class my_share_ptr
{
private:
    T* mptr;
    size_t* mcount;  //?? 为什么这里使用的是指针,为了多个指针对象共享引用数据
public:
    //初始化
    my_share_ptr(T* _ptr=nullptr):mptr(_ptr)
    {
        if(mptr){
            mcount = new size_t(1);
        }
        else {
            mcount = new size_t(0);
        }
    }
    //拷贝构造
    my_share_ptr(const my_share_ptr& ptr)
    {
        if(this == &ptr) return;  //自我不可拷贝
        this->mptr = ptr.mptr;
        this->mcount=ptr.mcount;
        (*this->mcount)++;
    }
    //赋值运算符
    my_share_ptr& operator=(const my_share_ptr& ptr)
    {
        if(this->mptr == ptr.mptr) return *this;
        //! =左边所指对象引用计数减一(因为左边的智能指针指向其他人了,所以指向原来的对象的次数需要减一)
        if(this->mptr){
            (*this->mcount)--;
            if(*this->mcount == 0){
                delete this->mptr;
                delete this->mcount;
            }
        }
        this->mptr=ptr.mptr;
        this->mcount=ptr.mcount;
        (*this->mcount)++; //应该理解为 (*(this->mcount))++;
        return *this;  //返回引用的对象就是取指针
    }
    // 重载*
    T& operator*(){
        assert(this->mptr == nullptr);
        return *(this->mptr);
    }
    // 重载->
    T* operator->(){
        assert(this->mptr == nullptr);
        return this->mptr;
    }
    // 析构函数
    ~my_share_ptr()
    {
        if(*this->mcount == 0){
            delete this->mptr;
            delete this->mcount;
            std::cout<<"释放"<<std::endl;
        }
        else {
            (*(this->mcount))--;
        }
        if(*(this->mcount) == 0)
        {
            delete this->mptr;
            delete this->mcount;
            std::cout<<"释放"<<std::endl;
        }
        
    }
    size_t use_count(){
        return *(this->mcount);
    }
};

int main()
{
    my_share_ptr<int> sp1(new int(3));
    my_share_ptr<int> sp2(sp1);
    my_share_ptr<int> sp3(new int(3));
    sp2 = sp3;
    cout << sp1.use_count() << endl;
    cout << sp3.use_count() << endl;
    return 0;
}


/**  输出
1
2
释放
释放
*/

2 📖 auto_ptr<T>仿写(C17的时候被废除了)

auto_ptr : 获得指针的独有权,auto_ptr自我类型的赋值、拷贝构造,实际上都是对象指针的独占。
不允许两个auto_ptr类型指向一个对象,会将原来的auto_ptr的指针指向null

#include <bits/stdc++.h>
// using namespace std;

template<typename T>
class my_auto_ptr {
public:
    explicit my_auto_ptr(
            T *ptr = nullptr) noexcept
            : mptr_(ptr) {}
 
    ~my_auto_ptr() noexcept {
        delete mptr_;
    }
	// 返回值为T&,允许*ptr=10操作
    T &operator*() const noexcept { return *mptr_; }
 
    T *operator->() const noexcept { return mptr_; }
 
    operator bool() const noexcept { return mptr_; }
 
    T *get() const noexcept { return mptr_; }
 
    // 拷贝构造,被复制方释放原来指针的所有权,交给复制方
    my_auto_ptr(my_auto_ptr &other) noexcept {
        mptr_ = other.release();
    }
 
    // copy and swap
    my_auto_ptr &operator=(my_auto_ptr &rhs) noexcept {
//        my_auto_ptr tmp(rhs.release());
//        tmp.swap(*this);
        // s上述两行等价于下面一行
        my_auto_ptr(rhs.release()).swap(*this);
        return *this;
    }
 
    // 原来的指针释放所有权
    T *release() noexcept {
        T *ptr = mptr_;
        mptr_ = nullptr;
        return ptr;
    }
 
    void swap(my_auto_ptr &rhs) noexcept {
        using std::swap;
        swap(mptr_, rhs.mptr_);    // 转移指针所有权
    }
 
private:
    T *mptr_;
};
 
template<typename T>
void swap(my_auto_ptr<T> &lhs, my_auto_ptr<T> &rhs) noexcept {
    lhs.swap(rhs);
}
 



int main() {
    my_auto_ptr<int> ptr1(new int(3));
    my_auto_ptr<int> ptr2{ptr1};
    if (ptr1.get() == nullptr && ptr2.get())
        std::cout << "拷贝构造:ptr1释放了所有权,ptr2获得了所有权" << std::endl;
    ptr1 = ptr1;
 
    my_auto_ptr<int> ptr3(new int(3));
    ptr1 = ptr3;
 
    if (ptr3.get() == nullptr && ptr1.get())
        std::cout << "赋值操作:始终只有一个对象管理一个区块!ptr3释放了所有权,ptr1获得了所有权" << std::endl;
}

3 📖 unique_ptr<T>仿写(C11引入)

前期注意:

六个特殊的成员函数其生成规则如下:

  • 默认构造函数,生成规则和C++98一样,在用户没有声明自定义的构造函数的时候并且编译期需要的时候生成。
  • 析构函数,生成规则和C++98一样,在C++11中有点不同的是,析构函数默认是noexcept。
  • 拷贝构造函数,用户自定义了移动操作会导致不生成默认的拷贝构造函数,其它和C++98的行为一致。
  • 拷贝赋值操作符,用户自定义了移动操作会导致不生成默认的拷贝赋值操作,其它和C++98的行为一致。
  • 移动构造函数和移动赋值操作符,仅仅在没有用户自定义的拷贝操作,移动操作和析构操作的时候才会生成。

unique_ptr : 独占智能指针,(C11引入,可认为是auto_ptr的功能升级版),可以粗浅的认为将auto_ptr里面的拷贝构造与拷贝赋值,改为移动构造与移动赋值。

除此之外:

  • 为了保证派生类能够向基类转化需要写一个模板拷贝构造函数;
  • 同时为了保证独占性,需要写一个非模板的拷贝构造函数,避免编译器生成默然拷贝构造函数,发生bitwise copy导致无法独占

unique_ptr与auto_ptr不同的是使用移动语义来显式的编写。对于auto_ptr可以说你随便赋值,但赋值完了之后原来的对象就不知不觉的报废,搞得你莫名其妙。而unique_ptr就干脆不让你可以随便去复制,赋值.如果实在想传个值,那就显式的说明内存转移(既使用std:move)。
然后这样传值完了之后,之前的对象也同样报废了.只不过多整个move让你明显的知道这样操作后会导致之前的unique_ptr对象失效。

#include <bits/stdc++.h>
using namespace std;

template<typename T>
class my_unique_ptr {
public:
    explicit my_unique_ptr(T* ptr=nullptr) noexcept 
        : mptr_(ptr) {}
    ~my_unique_ptr() noexcept{
        delete mptr_;
    }

    T& operator*() const noexcept {return *mptr_;}
    T* operator->() const noexcept {return mptr_;}

    // copy and swap  始终只有一个对象有管理这块空间的权限
    my_unique_ptr& operator=(my_unique_ptr rhs) noexcept {
        rhs.swap(*this);  //调用了 operator*()
        return *this ;
    }

    operator bool() const noexcept { return mptr_; }
    T* get() const noexcept { return mptr_; }

//TODO 若没有这个拷贝构造,会生成默认构造函数,会发生bitwise copy导致无法独占
    //拷贝构造函数,用户自定义了移动操作会导致不生成默认的拷贝构造函数,其它和C++98的行为一致。
    //声明了拷贝构造,这里会避免生成不可预料的其他默认构造函数,若没有这个便无法实现独占功能,具体参考unique_ptr_U.cpp里面的示例
    //这里限制拷贝构造传参:必须使用为右值,否则那就使用 std::move 修饰一下
    my_unique_ptr(my_unique_ptr &&other) noexcept{
        std::cout<<"move ctor"<<std::endl;
        mptr_ = other.release(); //释放原来的,返回新生成的
    }

    template<typename U> 
    my_unique_ptr(my_unique_ptr<U> &&other) noexcept {
        std::cout<<"template move ctor"<<std::endl;
        mptr_ = other.release();
    }


    T* release() noexcept {
        T* ptr = mptr_;
        mptr_ = nullptr;
        return ptr;
    }

    void swap(my_unique_ptr &rhs) noexcept {
        using std::swap;
        swap(mptr_,rhs.mptr_); // 转移指针所有权
    }

private:
    T* mptr_;
};

template<typename T>
void swap(my_unique_ptr<T> &lhs, my_unique_ptr<T> &rhs) {
    lhs.swap(rhs);
}



//!=========================  测试  =============================
class shape {
public:
    int m;
    virtual void print()
    {
        std::cout<<"this is shape"<<std::endl;
    }
};

class circle:public shape {
public:
virtual void print()
{
    std::cout<<"this is circle"<<std::endl;
} 
};

class rectangle:public shape {
public:
virtual void print()
{
    std::cout<<"this is rectangle"<<std::endl;
} 
};



int main() {
    my_unique_ptr<shape> ptr1{new circle()};
//    my_unique_ptr<shape> ptr2{ptr1}; // error
    my_unique_ptr<shape> ptr2{std::move(ptr1)};    // ok   out: move ctor
    if (ptr2.get() != nullptr && ptr1.get() == nullptr)
        ptr2.get()->print();  //out : this is circle

    my_unique_ptr<shape> ptr3{new rectangle()};
//    ptr1 = ptr3;    // error
    ptr3 = std::move(ptr1); // ok  out: move ctor
//    my_unique_ptr<circle> cl{new shape()};  // error 因为返回的是shape 不能基类转子类
    my_unique_ptr<circle> cl{new circle()};
    my_unique_ptr<shape> ptr5(std::move(cl));  // ok unique<circle>转unique<shape>  //out : template move ctor
}

/*
out:
-----
move ctor
this is circle
move ctor
template move ctor
*/

10 📖 参考资源

本文章对其他人的文章多有参考,但是一时半会儿找不到了,所以…

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值