C++智能指针原理

简介

智能指针就是对指针进行封装,使其提供特有的功能。

unique_ptr:封装了原始指针使其只能在同一时刻被同一对象拥有,并且在离开作用域时会自动销毁。

shared_ptr: 封装了原始指针,利用引用技术技术,实现多个对象同时共享一个指针,并且在所有对象都离开作用域时释放内存.

weak_ptr : 用来解决shared_ptr带来的循环计数问题,而且weak_ptr中的lock函数保证是线程安全

实现一个简单的unique_ptr

unique_ptr主要功能 :

  1. 不能够赋值,拷贝,允许移动
  2. operator* 实现
  3. operator→ 实现
  4. default_delete实现
  5. reset, release, get实现
template <class Tp, class Dp = default_delete>
class unique_ptr {
public:
    typedef Tp element_type;
    typedef Delete delete_type;
    typedef element_type* pointer;
    typedef element_type& reference;
public:
    unique_ptr(pointer data = nullptr) : data_(data) {}
    unique_ptr(pointer data, delete_type del) : data_(data), del_(del) {}

    ~unique_ptr() { clear(); }

    // 不允许赋值,拷贝
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

    // 允许移动
    unique_ptr(unique_ptr&& up)
        : data_(up.relase()), del_(up.del_) {}

    unique_ptr& operator=(unique_ptr&& up) {
        if (&up != *this) {
            reset(up.release());
            del_ = up.del_;
        }
        return *this;
    }
private:
    Tp* data_;
    Dp del_;
};

上面的代码实现了第一步,不允许赋值拷贝,允许移动,代码非常简单,unqiue_ptr有两个模板参数,第一个是指针类型,第二个参数就是对应着这个指针的删除器

reset, release和clear实现 :

pointer release() noexcept{
    auto res = data_;
    data_ = nullptr;
    return res;
}

void reset(pointer p = pointer()) noexcept {
    auto tmp = data_;
    data_ = p;
    if (tmp)
        del_(tmp);
}

void clear() const {
    del_(data_);
    data_ = nullptr;
}

elease函数的作用就是放弃原生指针的所有权,reset的功能是用一个新指针来替换原来的指针

operator*, operator→实现:

reference operator*() const { return (*data_); }
pointer operator->() const noexcept { return data_; }

defalut_delete实现 :

template
struct default_delete {
    void operator()(T* ptr) {
        if (ptr)
            delete ptr;
    }
};

template
struct default_delete {
    void operator()(T* ptr) {
        if (ptr)
            delete []ptr;
    }
}

这两个default_delete利用模板偏特化技术分别实现了删除普通指针以及数组指针, 到这里unique_ptr就已经实现完了,代码并不复杂,很简单,最后来看一个make_unique

tempalte
unique_ptr make_unique(Args&&... args)
{
    return unique_ptr(new T(std::forward(args)...));
}
  1. 引用计数
  2. operator*
  3. operator→
  4. 赋值拷贝构造函数

引用计数实现:

template
class RefCount {
    friend ClassName;
    RefCount(T* p) : pointer_(p), count(1) {}
    ~RefCount() { delete pointer_; }

    T* pointer_;
    size_t count_;
};

这个引用计数实现的非常简单,仅仅包含了一个指针和一个计数值,真正的操作放到了Shared_ptr里面了

operator*, operator→, 赋值,拷贝构造函数实现:

template
class shared_ptr {
public:
    typedef T value_type;
    typedef T* pointer;
    typedef T& reference;
public:
    shared_ptr(value_type* ptr) : ptr_(new RefCount(ptr)) {}

    shared_ptr(const shared_ptr& sp) : ptr_(sp) {
        ++ptr_->count_;
    }
    shared_ptr& operator=(const shared_ptr& rhs) {
        if (&rhs == this) {
            // 由于rhs需要赋值给*this,所以将rhs的计数先+1
            ++rhs.ptr_->count_;
            // 由于*this 需要抛弃掉本身保存的指针,所以将计数-1并判断是否已经是最后一个
            if (--ptr_->count_ == 0)
                delete ptr_;
            ptr_ = rhs.ptr_;
        }
        retur *this;
    }

    ~shared_ptr() {
        if (--ptr_->count_ == 0)
            delete ptr_;
    }

    reference operator*() const { return *ptr_->pointer_;}
    pointer operator->() const { return &(operator*()); }
private:
    RefCount* ptr_;
};

shared_ptr中最主要的就是拷贝构造和赋值运算符以及析构函数中对count进行的管理,在析构的时候需要将count减1,并判断是否为0,为0就表示当前是最后一个引用这个指针的shared_ptr,需要释放资源,在赋值和拷贝时都需要将计数+1, 在来看一下make_shared.

template
shared_ptr make_shared(Args&&... args> {
    return shared_ptr(new T(std::forward(args)...));
}

由上面我们实现的代码可以看出,在对count进行操作时不是多线程安全的,在标准库的实现中引用计数是线程安全的,它在底层用的是原子操作,也就是说在多线程情况下它只会释放一次也时线程不安全的,但是在构造,swap,reset操作中不是线程安全的,所以在多线程中共享shared_ptr需要格外的小心,要么加锁来保证安全,或者用weak_ptr来代替shared_ptr

#include 
#include 
using namespace std;

class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
shared_ptr pb;
void doSomthing()
{
}

~A()
{
cout << "kill A\n";
}
};

class B
{
public:
shared_ptr pa;
~B()
{
cout <<"kill B\n";
}
};

int main(int argc, char** argv)
{
shared_ptr sa(new A());
shared_ptr sb(new B());
if(sa && sb)
{
sa->pb=sb;
sb->pa=sa;
}
cout<<"sa use count:"<endl;
return 0;
}()

C++智能指针原理

上面的代码运行结果为:sa use count:2, 注意此时sa,sb都没有释放,产生了内存泄露问题!!

为什么产生内存泄漏 : 由于A里面有一个B的shared_ptr, 所以在A析构之前B必须析构,但是是B里面又有一个A的shared_ptr, 所以在B析构之前,A必须析构,有木有发现逻辑全乱了,就是这样就导致了循环引用,也就内存泄漏了.

#include 
#include 
using namespace std;

class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
    weak_ptr pb;
    void doSomthing()
    {
        shared_ptr pp = pb.lock();
        if(pp)//通过lock()方法来判断它所管理的资源是否被释放
        {
            cout<<"sb use count:"<endl;
        }
    }

    ~A()
    {
        cout << "kill A\n";
    }
};

class B
{
public:
    shared_ptr pa;
    ~B()
    {
        cout <<"kill B\n";
    }
};

int main(int argc, char** argv)
{
    shared_ptr sa(new A());
    shared_ptr sb(new B());
    if(sa && sb)
    {
        sa->pb=sb;
        sb->pa=sa;
    }
    sa->doSomthing();
    cout<<"sb use count:"<endl;
    return 0;
}()()

C++智能指针原理

Original: https://www.cnblogs.com/gd-luojialin/p/15028157.html
Author: gd_沐辰
Title: C++智能指针原理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535556/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • libj 0.8.2 发布,Java/JavaScript API 的 C++ 实现

    libj 0.8.2 增加了一些新的字符串相关的方法。 libj 是一个跨平台的运行库,相当于提供了类似 Java/JavaScript API。libj 的内存管理是自动的,基于…

    C++ 2023年5月29日
    048
  • 聊聊 C++ 中的四种类型转换符

    一:背景 在玩 C 的时候,经常会用 void* 来指向一段内存地址开端,然后再将其强转成尺度更小的 char* 或 int* 来丈量一段内存,参考如下代码: int main()…

    C++ 2023年5月29日
    069
  • C++ 知识点

    知识点 说明 所谓的引用就是给变量取一个别名,使一块内存空间可以通过几个变量名来访问。声明引用类型的变量需要在变量名前加上符号&,并且必须指定初值,即被引用的变量。 C++…

    C++ 2023年5月29日
    050
  • C/C++定义全局变量/常量几种方法的区别

    在讨论全局变量之前我们先要明白几个基本的概念: 编译单元(模块):在IDE开发工具大行其道的今天,对于编译的一些概念很多人已经不再清楚了,很多程序员最怕的就是处理连接错误(LINK…

    C++ 2023年5月29日
    073
  • (筆記) 如何寫入binary file某個byte的值? (C/C++) (C)

    Abstract通常公司為了保護其智慧財產權,會自己定義檔案格式,其header區會定義每個byte各代表某項資訊,所以常常需要直接對binary檔的某byte直接進行寫入。 In…

    C++ 2023年5月29日
    048
  • [转]C++ 类中的static成员的初始化和特点

    在C++的类中有些成员变量初始化和一般数据类型的成员变量有所不同。以下测试编译环境为: cpp;gutter:true; ➜ g++ -v Using built-in specs…

    C++ 2023年5月29日
    061
  • [C++] 引用类型&

    引用的方法: 类型 &引用名 = 变量名; 例如: int a = 5; int &b = a; 引用的规则: 1、引用被创建的同时必须被初始化 2、无null引用…

    C++ 2023年5月29日
    056
  • c++为什么要面向对象?

    前言 c和c++的区别是什么?不可置否,最重要的就是c++的编程思想是面向对象,而c的编程思想是面向过程,这是它们的本质区别,如果你在使用c++编程时使用的还是面向过程的编程思想,…

    C++ 2023年5月29日
    060
  • [C++] 左值、右值、右值引用

    lvalue 代表了对象,可通过取地址符获取地址,可赋值。L 可看做 location。 rvalue 代表了数据,不能获取内存地址,不可赋值。 rvalue 不能当做 lvalu…

    C++ 2023年5月29日
    050
  • 聊聊 C++ 和 C# 中的 lambda 玩法

    这几天在看 C++ 的 lambda 表达式,挺有意思,这个标准是在 C11&#x6807;&#x51C6; 加进去的,也就是 2011 年,相比 C# 2007 …

    C++ 2023年5月29日
    053
  • C++实现图的遍历和最短路径

    摘自:https://blog.csdn.net/qq_45694646/article/details/106764026 C++实现图的基本操作 数据结构之图(存储,遍历,最短…

    C++ 2023年5月29日
    070
  • c++ set与unordered set的区别

    c++ std中set与unordered_set区别和map与unordered_map区别类似,其底层的数据结构说明如下: 1、set基于红黑树实现,红黑树具有自动排序的功能,…

    C++ 2023年5月29日
    050
  • EclipseC++学习笔记-1 环境搭建

    最近一个项目使用的EclipseC++编写的,所以需要搭建一个EclipseC++平台开发。1、windows下载https://www.eclipse.org/downloads…

    C++ 2023年5月29日
    081
  • Google C++ 单元测试 GTest

    from : http://www.cnblogs.com/jycboy/p/6057677.html 一、设置一个新的测试项目 在用google test写测试项目之前,需要先编…

    C++ 2023年5月29日
    036
  • C++11 并发指南五(std::condition_variable 详解)

    std::condition_variable 是条件变量,更多有关条件变量的定义参考维基百科。Linux 下使用 Pthread 库中的 pthread_cond_*() 函数提…

    C++ 2023年5月29日
    061
  • How to: Create a C/C++ Union by Using Attributes (C#)

    【 How to: Create a C/C++ Union by Using Attributes (C#)】 1、you can create what is known as…

    C++ 2023年5月29日
    062
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球