C++经典面试题之深入解析智能指针unique_ptr的实现原理与自定义实现

一、unique_ptr 概述

① unique_ptr 的特点

  • unique_ptr 独享它指向的对象,也就是说,同时只有一个 unique_ptr 指向同一个对象,当这个 unique_ptr 被销毁时,指向的对象也随即被销毁。这也是它和 shared_pt r不一样的地方,它不需要做引用计数,也不可以被第二个人引用,只有它自己。

② unique_ptr 的用途

  • 作为一个类的成员变量,这个变量只在本类使用,不会被赋值给其他类,也不会作为参数传递给某个函数;
  • 在一个函数作为局部变量,使用完就不用再管,函数结束,自动释放托管资源。

③ unique_ptr 原理

  • 构造时传入托管对象的指针,析构时 delete 对象,这是为了要对得其智能指针的名号,即主动删除托管对象;
  • 禁用赋值函数,这是为了要唯一性,那就禁用赋值,不能再让别人引用它。那么怎么实现呢?很简单,把两种赋值函数,都设置为 delete,如下所示:
    unique_ptr(const unique_ptr<T>&) noexcept = delete;
    unique_ptr& operator = (const unique_ptr&) noexcept = delete;
  • 使用“=delete”修饰,表示函数被定义为 deleted,也就意味着这个成员函数不能再被调用,否则编译就会出错。当然,虽然赋值禁用了,但是愿意交出控制权,交给赋值的人,那还是可以允许的。
unique_ptr(unique_ptr&& move) noexcept {
        std::cout << "construct for unique_ptr&&" << std::endl;
        move.swap(*this);
    }
    unique_ptr& operator=(unique_ptr&& move) noexcept {
        move.swap(*this);
        return *this;
    }
  • 参数 move 的类型,是一个右值引用,其实就是 std::move 的返回值,如下所示:
unique_ptr<Test> tPtr1(new Test());
unique_ptr<Test> tPtr3(std::move(tPtr1));
  • 这种情况下,是可以编译过的,只不过,tPtr1 的资源,如其成员变量将无法再调用,调用的话就崩溃,因为它已经全部交给 tPtr3。

二、自定义实现

  • UniquePtr.h:
#include <utility>
#include<iostream>

/****
 * 智能指针 unique_ptr 的简单实现
 * 特点:独享它指向的对象也就是说,同时只有一个 unique_ptr 指向同一个对象,当 unique_ptr 被销毁时,指向的对象也随即被销毁
 * 
 * 典型用途:
 * 1. 在一个函数定义一个 A* ptr = new A(), 结束还需要用 delete,而用 unique_ptr,就不需要自己调用 delete
 * 2. 作为一个类的变量,这个变量只在本类使用,不会被其他类调用,也不会作为参数传递给某个函数
 */
template<typename T>
class unique_ptr {
private:
    T * ptr_resource = nullptr;

public:
    // explicit构造函数是用来防止隐式转换, 即不允许写成unique_ptr<T> tempPtr = T;
    // std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能
    // move之后,raw_resource内部的资源将不能再被raw_resource使用
    explicit unique_ptr(T* raw_resource) noexcept : ptr_resource(std::move(raw_resource)) {}
    unique_ptr(std::nullptr_t) : ptr_resource(nullptr) {}

    unique_ptr() noexcept : ptr_resource(nullptr) {}

    // 析构时,释放托管的对象资源
    ~unique_ptr() noexcept {
        delete ptr_resource;
    }
    // Disables the copy/ctor and copy assignment operator. We cannot have two copies exist or it'll bypass the RAII concept.
    // 重要,禁止两种拷贝的赋值方式
    // 使用"=delete"修饰,表示函数被定义为deleted,也就意味着这个成员函数不能再被调用,否则就会出错。
    unique_ptr(const unique_ptr<T>&) noexcept = delete;
    unique_ptr& operator = (const unique_ptr&) noexcept = delete;

public:
    // && 是右值引用,允许移动语义,虽然无法复制 unique_ptr,但可以安全地移动
    // 例子:unique_ptr<Test> tPtr3(std::move(tPtr1));
    unique_ptr(unique_ptr&& move) noexcept {
        std::cout << "construct for unique_ptr&&" << std::endl;
        move.swap(*this);
    }
    // ptr = std::move(resource)
    unique_ptr& operator=(unique_ptr&& move) noexcept {
        std::cout << "operator= for unique_ptr&&" << std::endl;
        move.swap(*this);
        return *this;
    }

    explicit operator bool() const noexcept {
        return this->ptr_resource;
    }
    // releases the ownership of the resource. The user is now responsible for memory clean-up.
    T* release() noexcept {
        return std::exchange(ptr_resource, nullptr);
    }
    // returns a pointer to the resource
    T* get() const noexcept {
        return ptr_resource;
    }
    // swaps the resources
    void swap(unique_ptr<T>& resource_ptr) noexcept {
        std::swap(ptr_resource, resource_ptr.ptr_resource);
    }
    // reset 删除老的,指向新的
    void reset(T* resource_ptr) noexcept(false) {
        // ensure a invalid resource is not passed or program will be terminated
        if (resource_ptr == nullptr)
            throw std::invalid_argument("An invalid pointer was passed, resources will not be swapped");

        delete ptr_resource;

        ptr_resource = nullptr;

        std::swap(ptr_resource, resource_ptr);
    }
public:
    // overloaded operators
    T * operator->() const noexcept {
        return this->ptr_resource;
    }
    T& operator*() const noexcept {
        return *this->ptr_resource;
    }
    // 额外说明noexcept
    // noexcept C++11关键字,告诉编译器,函数中不会发生异常,有利于编译器对程序做更多的优化
    // C++中的异常处理是在运行时而不是编译时检测的,为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化
};
  • 主程序 main.cpp:
#include "UniquePtr.h"

// 简单的类,将被智能指针使用
class Test {
public:
    Test() {
        std::cout << "Test class construct" << std::endl;
    }
    ~Test() {
        std::cout << "Test class destruct" << std::endl;
    }

    void printSomething() {
        std::cout << "Test printSomething " << std::endl;
    }

    void printResource() {
        std::cout << "Test printResource " << a << std::endl;
    }

    int getResource() {
        return a;
    }

private:
    int a = 10;
};

// 使用unique_ptr的类
class PUser {
public:
    PUser() {
        //初始化pTest
        pTest.reset(new Test());
        std::cout << "PUser construct " << std::endl;
    }
    ~PUser() {
        std::cout << "PUser destruct" << std::endl;
    }

    //可以在类的各种函数,使用pTest,
    void userTest() {
        std::cout << "userTest " << pTest->getResource() << std::endl;
    }

private:
    // 典型用法,在一个类中,作为一个类成员变量
    unique_ptr<Test> pTest;
};

// 主程序入口
int main(int argc, char* argv[]) {
    unique_ptr<Test> tPtr1(new Test());
    // 编译就不通过,因为已经定义,unique_ptr& operator = (const unique_ptr&) noexcept = delete;
    // unique_ptr<Test> tPtr2 = tPtr1;
    // unique_ptr<Test> tPtr3(tPtr1);

    // 以下两句话就允许,因为pPtr1做了控制权转移
    unique_ptr<Test> tPtr3(std::move(tPtr1));
    unique_ptr<Test> tPtr4 = std::move(tPtr3);

    // tPtr1->printResource(); // 这一句就崩溃,因为tPtr1非空,只不过资源完全不能用了
    tPtr1->printSomething();   // 这一句不崩溃,tPtr1虽然资源不能用,但是代码段可以调用,只要代码段没有使用到资源

    PUser* pUser = new PUser();
    pUser->userTest();

    return 0;
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
unique_ptrC++11 中引入的智能指针,用于管理动态分配的对象。 unique_ptr 的特性是:它是唯一拥有(unique ownership)被管理对象的智能指针,也就是说,同一时间只能有一个 unique_ptr 指向一个对象。当 unique_ptr 被销毁时,它会自动释放所管理的对象内存。 下面是 unique_ptr 的简单实现: ```cpp template <typename T> class unique_ptr { public: unique_ptr(T* ptr = nullptr) : m_ptr(ptr) {} ~unique_ptr() { delete m_ptr; } unique_ptr(const unique_ptr&) = delete; unique_ptr& operator=(const unique_ptr&) = delete; unique_ptr(unique_ptr&& other) noexcept : m_ptr(other.m_ptr) { other.m_ptr = nullptr; } unique_ptr& operator=(unique_ptr&& other) noexcept { if (this != &other) { delete m_ptr; m_ptr = other.m_ptr; other.m_ptr = nullptr; } return *this; } T* get() const { return m_ptr; } T* operator->() const { return m_ptr; } T& operator*() const { return *m_ptr; } private: T* m_ptr; }; ``` 这是一个简化版本的 unique_ptr,它包含了基本的功能,如构造函数、析构函数、移动构造函数、移动赋值运算符,以及 get()、operator->() 和 operator*() 方法。 需要注意的是,这个实现并不完整,只是为了演示 unique_ptr 的基本原理和用法。实际使用时,应该考虑更多的细节,如空指针检查、自定义删除器等。另外,C++11 中已经提供了标准库中的 unique_ptr 实现,我们通常会使用标准库中的智能指针而不是自己实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╰つ栺尖篴夢ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值