以下知识和代码来自Light-City/CPlusPlusThings: C++那些事 (github.com)

为什么需要智能指针

void test(bool isEmpty)
{
    int* p=new int();
    if(isEmpty) return;
    ...
    delete p;
}

如上面的代码所示如果isEmpty为true,程序就会提前返回,p所指向的内存就无被delete。随着使用程序时间增加,内存占用会越来越大。因此手动管理指针可能会带来问题,而智能指针就是为了解决这个问题诞生的。

智能指针的实现

智能指针本质上就是利用RALL(资源获取即初始化)原理来实现的,通过析构函数在超出对象作用域后自动释放指针内存。

template<typename T>
class unique_ptr {
public:
    explicit unique_ptr(
        T* ptr = nullptr) noexcept
        : ptr_(ptr) {
    }

    ~unique_ptr() noexcept {
        delete ptr_;
    }
    ...
};

unique_ptr实现

我们来看如下代码

#include <iostream>

class RawPointerWrapper {
public:
    int* data;

    RawPointerWrapper(int value) {
        data = new int(value);
        std::cout << "Resource allocated at: " << data << std::endl;
    }

    ~RawPointerWrapper() {
        std::cout << "Deleting resource at: " << data << std::endl;
        delete data;
    }
};


int main() {
    RawPointerWrapper w1(10);
    RawPointerWrapper w2 = w1;
    return 0;
}

运行后结果为:

Resource allocated at: 00000228FCD95F70
Deleting resource at: 00000228FCD95F70
Deleting resource at: 00000228FCD95F70

代码中c++编译器默认为类添加了“浅拷贝”版本的拷贝构造函数。因此w1在赋值给w2时只是讲指针地址给了w2,w2和w1指向了同一块内存,程序会对同一块地址delete两次,这会导致程序的崩溃。而unique_ptr就能避免这种情况,它是一种“独占”型指针,意味着同一时间只能有一个unique_ptr指向一块内存地址,在我们的实现代码中,就需要禁止拷贝构造函数和拷贝赋值运算符。

#include<iostream>

template<typename T>
class unique_ptr {
public:
    explicit unique_ptr(
        T* ptr = nullptr) noexcept
        : ptr_(ptr) {
    }

    ~unique_ptr() noexcept {
        delete ptr_;
    }
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
    ...
};

禁用了“拷贝”后,当涉及指针的交换,我们就要“移动”指针,并将被移动的指针置空。这时移动构造函数和移动赋值运算符就登场了。

unique_ptr(unique_ptr&& other) noexcept {
    ptr_ = other.release();
}

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

// 原来的指针释放所有权
T* release() noexcept {
    T* ptr = ptr_;
    ptr_ = nullptr;
    return ptr;
}

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

到这里我们的unique_ptr就差不多完成了,以下是完整代码。

#include<iostream>

template<typename T>
class unique_ptr {
public:
    explicit unique_ptr(
        T* ptr = nullptr) noexcept
        : ptr_(ptr) {
    }

    ~unique_ptr() noexcept {
        delete ptr_;
    }
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

    T& operator*() const noexcept { return *ptr_; }

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

    operator bool() const noexcept { return ptr_; }

    T* get() const noexcept { return ptr_; }

    unique_ptr(unique_ptr&& other) noexcept {
        ptr_ = other.release();
    }

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

    // 原来的指针释放所有权
    T* release() noexcept {
        T* ptr = ptr_;
        ptr_ = nullptr;
        return ptr;
    }

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

private:
    T* ptr_;
};
template<typename T>
void swap(unique_ptr<T>& lhs, unique_ptr<T>& rhs) {
    lhs.swap(rhs);
}

int main() {
    unique_ptr<int> ptr(new int(5));
    return 0;
}

为什么代码中有两个swap

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

在我们的代码中我们写了一个全局模板的swap,有人可能会觉得这多此一举。但如果你不写这个函数,当我们定义两个unqiue_ptr指针并使用swap交换时,swap函数并不知道我们的unique_ptr内部长什么样,它就会用最保险的方式即套用通用公式。

// std::swap 的通用逻辑
T temp = std::move(ptr1); 
ptr1 = std::move(ptr2);   
ptr2 = std::move(temp);  

此时会涉及三次移动。如果加上我们的全局模板的swap,编译器就会通过ADL机制找到我们的全局模板的swap,最后通过lhs.swap(rhs)调用我们类中的成员函数swap直接交换两个指针地址从而避免三次移动,使我们代码效率更高。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com