服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

C++ 面试:说说智能指针 unique_ptr 实现原理

日期: 来源:字节流动收集编辑:

C++ 智能指针 unique_ptr 原理与自定义实现

先说其特点:  

独享它指向的对象。也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁。  

这也是它和shared_ptr不一样的地方。它不需要做引用计数,也不可以被第二个人引用。只有它自己。如果想引用?没用的,编译都编译不过!

再说其典型用途

  1. 作为一个类的成员变量,这个变量只在本类使用,不会被赋值给其他类,也不会作为参数传递给某个函数

  2. 在一个函数作为局部变量,使用完就不用再管,函数结束,自动释放托管资源

最后说原理:  
a) 构造时传入托管对象的指针,析构时delete对象  
b) 禁用赋值函数

a) 是为了要对得其智能指针的名号,即,帮你主动删除托管对象。

b) 是为了要对得起unique的名号,唯一啊唯一,那就禁用赋值,不能再让别人引用它!

    unique_ptr(const unique_ptr<T>&) noexcept = delete;
    unique_ptr& operator = (const unique_ptr&) noexcept = delete;

b 怎么实现呢,如上,很简单,把两种赋值函数,都设置为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:
    //&& 是右值引用,见https://zhuanlan.zhihu.com/p/107445960
    // 允许移动语义。虽然无法复制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;
}

参考

https://github.com/laxodev/Unique-pointer-implementation

原文链接: https://blog.csdn.net/newchenxf/article/details/116274506



-- END --


进技术交流群,扫码添加我的微信:Byte-Flow



获取相关资料和源码



推荐:

面试常问的 C/C++ 问题,你能答上来几个?

C++ 面试必问:深入理解虚函数表

很多人搞不清 C++ 中的 delete 和 delete[ ] 的区别

看懂别人的代码,总得懂点 C++ lambda 表达式吧

Java、C++ 内存模型都不知道,还敢说自己是高级工程师?

C++ std::thread 必须要熟悉的几个知识点

现代 C++ 并发编程基础

现代 C++ 智能指针使用入门

c++ thread join 和 detach 到底有什么区别?

C++ 面试八股文:list、vector、deque 比较

C++经典面试题(最全,面中率最高)

C++ STL deque 容器底层实现原理(深度剖析)

STL vector push_back 和 emplace_back 区别

了解 C++ 多态与虚函数表

C++ 面试被问到的“左值引用和右值引用”


觉得不错,点个在看呗~

相关阅读

  • Vue2与Vue3响应式原理与依赖收集详解

  • 前言继 Angular 和 React 之后,尤大在 2016 年发布了如今“前端三剑客”之一的 Vue 2.0,并凭借其简单易用、轻量高效的特点受到了广泛的欢迎,特别是在国内环境中。然而 Vue 2
  • 博士找对象和找大学教职哪个更难?

  • 都说博士脱单难,又说高校教职一位难求,究竟博士找对象和找大学教职,哪个更难?一找对象和找教职的共同点01.匹配找对象和找教职,都是双方匹配挑选的过程,双方带着一定条件和目标,寻
  • 连城司法:“张弛有度”护企安商促发展

  • (记者:黄水林 通讯员:江秋蓉)“感谢县司法部门批准我跨县市活动,让我公司起死回生。”近日,连城县司法局联合检察院开展涉企社矫对象走访,从事牛养殖、加工的吴某在介绍其企业生产
  • 你知道初中级前端怎么突破技术瓶颈吗?

  • 模拟面试、简历指导可私信找我,最低的价格收获最高的指导~已帮助50+名同学完成改造!前言大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的
  • 我在函数式编程上犯下的几个错误

  • 【CSDN 编者按】提到编程思想,你首先想到的会是面向对象还是面向函数编程呢?本文作者分享了自己在函数式编程实践中踩过的一些坑,分享给大家,希望能对你有所帮助。原文链接:https
  • 详解Flask框架SSTI攻击的利用与绕过技巧

  • Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。Flask也被称为 “microframework” ,因为
  • ​自动控制原理(深入理解自动控制框架)

  • 点击下方卡片,关注“新机器视觉”公众号重磅干货,第一时间送达编辑丨古月居1. 控制原理1.1 开环与闭环系统下面是开环系统与闭环系统的示例。以给水壶加热的过程举例,开环系统
  • vivo AI 计算平台的 K8s 分级配额管理实践

  • 作者 | 刘东阳
    审校 | 赵钰莹 2018 年底,vivo AI 研究院为了解决统一高性能训练环境、大规模分布式训练、计算资源的高效利用调度等痛点,着手建设 AI 计算平台。经过四年
  • 【面经】互联网寒冬,三年经验,前端面试~

  • 模拟面试、简历指导可私信找我,最低的价格收获最高的指导~已帮助50+名同学完成改造!前言大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • “黑马”基金经理来了!

  • 中国基金报记者 方丽 孙晓辉 路演都排不过来,是今年春节后不少绩优基金经理都面临的现状。 最近这一势头又开始蔓延到平时关注度不太高、规模不太
  • 去年上海涨价最好的是哪些板块

  • 过去一年,上海哪些板块二手涨幅最大这是今天想要和各位分享的一个关于上海二手市场的数据房价涨幅的真实情况,相信也是各位在挑选二手房的时候,最关心的一个问题在年末的时候,我
  • C++ 面试:说说智能指针 unique_ptr 实现原理

  • C++ 智能指针 unique_ptr 原理与自定义实现先说其特点: 独享它指向的对象。也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁
  • 那些高低配小区的房子,到底能不能买

  • 如何看待高低配类产品这确实也是在目前一手房市场,或者说很多次新房经常会看到的小区的类型首先我们要知道什么是高低配高低配就是严格意义上,整个小区既有低密度的别墅或者叠
  • 说点实际的

  • 1、行情。今天,市场微跌,又是波澜不惊的一天。投资大多数时候就是这样,无事可做,需要耐心等待。股谚有云:“牛市里撑死胆大的,饿死胆小的,冤死折腾的!”要想在牛市赚钱,初期要胆大,敢
  • 二级市场捡辣鸡冠军丨红利快新高了

  • —— 前言 ——本文研读的是某位神秘人士的微博。由于有些人有洁癖,要求不能公开神秘人士真实身份,那就不公开吧。有群众的地方就开始有崇拜,有了崇拜就开始搞狂热……不过,该