嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 如何实现自己的C++ unique_ptr?

如何实现自己的C++ unique_ptr?


01

unique_ptr简介


只要用了C++11以及以上,我都推荐你尽可能的使用智能指针代替裸指针,今天我们要讲的是独占所有权的unique_ptr。unique_ptr是通过指针占有并管理另一对象,并在uique_ptr离开作用域时释放该对象的智能指针。

在下列两者之一发生时发生智能指针释放关联对象的资源

  • unique_ptr离开了作用域

  • 通过operator=或reset()赋值另一指针给管理的unique_ptr对象。
unique_ptr独占所有权意味着无法通过复制的方式获取unique_ptr管理的对象指针,因为复制意味着内部对象指针有两个副本,互相不知道所指对象资源是否已释放,只能通过移动的方式获取对象的所有权,具体表现为unique_ptr的复制构造函数和复制运算符函数都是delete。

当创建unique_ptr类实例变量时若不指定对象删除器,则使用默认的删除器。

02

实现自己的unique_ptr

根据unique_ptr基本功能,自定义unique_ptr类模板声明如下:#ifndef UNIQUE_PTR_H
#define UNIQUE_PTR_H

namespace cpp_lib{

class Deletor{
public:
    Deletor() = default;

    template <typename U>
    void operator()(U *p) noexcept{
        if(p){
            delete p;
        }
    }
};

template <typename T, typename U = Deletor>
class unique_ptr{
public:
    explicit unique_ptr(); //显示构造函数
    explicit unique_ptr(T* p); //显示构造函数
    explicit unique_ptr(unique_ptr&& p); //移动构造函数
   
void operator=(unique_ptr&& p); //移动赋值运算符
    ~unique_ptr(); //析构函数

    unique_ptr(const unique_ptr& p) = delete; //不支持复制构造函数
    unique_ptr operator=(const unique_ptr& p) = delete; //不支持复制运算符函数
    unique_ptr operator=(T* p) = delete; //不支持裸指针赋值

    T* release();

    void reset(T* p = nullptr) noexcept;

    void swap(unique_ptr& rhs) noexcept;

    T* get() noexcept;

    U& get_deletor() noexcept;

    explicit operator bool() const noexcept;

    T& operator*() const noexcept;

    T* operator->() const noexcept;

private:
    T *ptr; //值指针
    U deletor; //删除器
};

}

#endif // UNIQUE_PTR_H

这里自定义了新的命名空间cpp_lib主要是为了防止和标准库里的unique_ptr冲突。

自定义unique_ptr构造函数定义如下:

template <typename T, typename U>
unique_ptr<T, U>::unique_ptr() : ptr(nullptr) { }

template <typename T, typename U>
unique_ptr<T, U>::unique_ptr(T *p) : ptr(p) { }

template <typename T, typename U>
unique_ptr<T, U>::unique_ptr(unique_ptr&& p){
    ptr = std::forward<T*>(p.ptr);
    deletor = std::forward<U>(p.deletor);
    p.ptr = nullptr;
}
移动构造函数中,被移动的对象已经失去了所有权,内部的对象指针一定要置为nullptr,否则错误使用会发生程序奔溃且调用operator bool()无法正确判断对象指针是否为空。

自定义unique_ptr移动赋值运算符函数定义:

template <typename T, typename U>
void unique_ptr<T, U>::operator=(unique_ptr&& p){
    ptr = std::forward<T*>(p.ptr);
    deletor = std::forward<U>(p.deletor);
    p.ptr = nullptr;
}
和移动构造函数很相似,被移动的对象已经失去了所有权,内部的对象指针一定要置为nullptr,否则错误使用会发生程序奔溃且调用operator bool()无法正确判断对象指针是否为空。
自定义unique_ptr析构函数定义如下所示:template <typename T, typename U>
unique_ptr<T, U>::~unique_ptr(){
    if(ptr){
        deletor(ptr);
    }
    ptr = nullptr;
}
在析构函数里,主要工作是释放对象的资源。
自定义unique_ptr其他成员函数定义如下:template <typename T, typename U>
T* unique_ptr<T, U>::release(){
    T* p = ptr;
    ptr = nullptr;
    return p;
}

template <typename T, typename U>
void unique_ptr<T, U>::reset(T* p) noexcept{
    if(ptr){
        deletor(ptr);
    }
    ptr = p;
}

template <typename T, typename U>
void unique_ptr<T, U>::swap(unique_ptr& rhs) noexcept{
    std::swap(ptr, rhs.ptr);
    std::swap(deletor, rhs.deletor);
}

template <typename T, typename U>
T* unique_ptr<T, U>::get() noexcept{
    return ptr;
}

template <typename T, typename U>
U& unique_ptr<T, U>::get_deletor() noexcept{
    return deletor;
}

template <typename T, typename U>
unique_ptr<T, U>::operator bool() const noexcept{
    if(ptr){
        return true;
    }else{
        return false;
    }
}

template <typename T, typename U>
T& unique_ptr<T, U>::operator*() const noexcept{
    return *ptr;
}

template <typename T, typename U>
T* unique_ptr<T, U>::operator->() const noexcept{
    return ptr;
}
03

自定义unique_ptr测试

下面我们写一个小程序来测试下自己实现的unique_ptr,代码如下:#include <iostream>
#include "unique_ptr.cpp"

class Widget{
public:
    Widget(){
        std::cout << "Widget::constructor" << std::endl;
    }
    ~Widget(){
        std::cout << "Widget::destructor" << std::endl;
    }
    void fun(){
        std::cout << "Widget::fun" << std::endl;
    }
};

class Deletor{
public:
    template <typename T>
    void operator()(T* p) noexcept{
        std::cout << "delete pointer" << std::endl;
        if(p){
            delete p;
        }
    }
};

int main()
{
    cpp_lib::unique_ptr<int> ptr(new int(1));

    *ptr = 2;
    if(ptr){
        std::cout << "ptr is not null" << std::endl;
    }

    std::cout << *ptr << std::endl;
    ptr.reset();
    if(!ptr){
        std::cout << "ptr is null" << std::endl;
    }

    cpp_lib::unique_ptr<Widget> w_ptr(new Widget());
    w_ptr->fun();

    cpp_lib::unique_ptr<Widget> w_ptr2;
    w_ptr2.reset(new Widget());
    w_ptr2->fun();
    auto p = w_ptr.release();
    p->fun();
    delete p;

    std::cout << "=========" << std::endl;
    cpp_lib::unique_ptr<Widget> w_ptr3(new Widget());
    cpp_lib::unique_ptr<Widget> w_ptr4;
    w_ptr4.swap(w_ptr3);
    w_ptr4->fun();

    std::cout << "=========" << std::endl;
    w_ptr3 = std::move(w_ptr4);
    w_ptr3->fun();
    std::cout << "#########" << std::endl;

    std::cout << "=========" << std::endl;
    cpp_lib::unique_ptr<Widget> w_ptr5(std::move(w_ptr3));
    w_ptr5->fun();
    std::cout << "#########" << std::endl;

    cpp_lib::unique_ptr<Widget, Deletor> w_ptr6(new Widget());

    return 0;
}

编译运行输出如下:

ptr is not null
2
ptr is null
Widget::constructor
Widget::fun
Widget::constructor
Widget::fun
Widget::fun
Widget::destructor
=========
Widget::constructor
Widget::fun
=========
Widget::fun
#########
=========
Widget::fun
#########
Widget::constructor
delete pointer
Widget::destructor
Widget::destructor
Widget::destructor
程序输出正确,且cpp_lib::unique_ptr离开作用于后自动调用析构函数释放所管理对象的资源。