C++智能指针再理解

虽然之前写过关于智能指针的,但是现在看来不够好,重新学习了一遍后又有了新的理解
在C++中,指针分为两种:

  • 原始指针(raw pointer)
  • 智能指针(smart pointer)

原始指针就是C/C++基础中的普通指针,而智能指针是C++11提出的:

智能指针是原始指针的封装,其优点是会自动分配内存,不用担心潜在的内存泄露。

只能指针目前有三种:unique_ptrshared_ptrweak_ptr,auto_ptr被删除了也就不说了。

并不是所有指针都可以封装成原始指针,很多时候原始指针要更方便;并且智能指针只解决了指针的一部分问题,而没有从根本上解决C++内存安全问题,仍然可能造成内存安全问题
在各类指针中,最常用的还是裸指针,在是unique_ptr和shared_ptr。
weak_ptr是对shared_ptr的一个补充,应用场景较少。

独占指针unique_ptr

从它的名字我们也能够直到它的特点:在任何时候,都只能有一个指针管理内存,并且,它利用了RAII思想,其生命周期由其作用域控制,当指针超出其作用域时,内存将自动释放。

unique_ptr具有三种创建方式:

  1. 通过已有的裸指针创建
  2. 通过new创建
  3. 通过std::make_unique()创建最推荐的方式

unique_ptr使用get函数获取其地址,其使用方式和裸指针的使用方式一样,可以使用“*”进行解引用,也可以使用“->”进行成员访问。

当unique_ptr作为函数参数或者返回值的时候,一定要注意所有权

所有权:对于一个资源(例如内存或对象),能够控制它的创建、使用和销毁的权力。所有权规定了资源在何时被分配和释放。

unique_ptr与函数

值传递的时候

由于使用值传递的时候,会创建副本,而unique_ptr是禁用拷贝构造函数的,因此我们需要使用std::move转移内存所有权;但是若是我们忘记了使用std::move呢?
> std::unique_ptr 是 std::move_constructible 和 std::movable 的,这意味着它可以通过 std::move 进行移动构造,并且可以被移动。
>当你传入一个 std::unique_ptr 到一个函数时,编译器会生成一个该 std::unique_ptr 的副本。这个副本实际上是一个右值引用,即使看起来你传入的是一个左值。因为 std::unique_ptr 的移动构造函数会接受一个右值引用,所以这个副本会被转换为一个右值引用,并调用 std::unique_ptr 的移动构造函数。这样,原始的 std::unique_ptr 会被移动到函数中,而不是复制。

引用传递的时候

==若是将unique_ptr设置为const,表示unique_ptr底层裸指针的指向不能被修改(shared_ptr也是一样的)。
例如:我们不能够使用reset()等

作为函数返回值
  1. 因为unique_ptr在出作用域的时候会被销毁,因此需要注意所有权转移,在函数返回前就需要将unique_ptr销毁,以避免资源泄露
  2. 因为unique_ptr支持移动语义,如果函数返回一个unique_ptr而调用者接收一个unique_ptr,它可以安全地接收返回值,因为所有权的转移按照移动语义执行。
  3. 避免返回局部对象的引用

不推荐使用原有的裸指针进行创建

unique_ptr有三种创建方式,但是第一种我们最好还是不要使用,这里来看一段代码:

#include <iostream>
#include <memory>
using namespace std;
int main(){
    int* r_ptr = new int(100);
    unique_ptr<int> u_ptr{r_ptr};

    /*
        查看两者指向的地址
    */
   cout << r_ptr << '\n';
   cout << u_ptr.get() << '\n';
}

可以发现,原来的裸指针还指向该地址的同时,智能指针也指向该地址,因此,unique_ptr实际上没有达到目的:单独维护一个指针,很容易出现误操作等问题。
若是我们还将裸指针delete了,智能指针中存储的将会空悬挂;如下:

#include <iostream>
#include <memory>
using namespace std;
int main(){
    int* r_ptr = new int(100);
    unique_ptr<int> u_ptr{r_ptr};

    /*
        查看两者指向的地址
    */
    cout << r_ptr << '\n';
    cout << u_ptr.get() << '\n';

    // 释放裸指针
    delete r_ptr;
    cout << *u_ptr;
}

实现一个简单的unique_ptr

以下是我对unique_ptr的一个简单实现:

/*
    由于只是为了理解uniuqe_ptr的内部原理
    这里就使用一个文件进行编写了
    这么做不是很规范,但是请允许我偷个懒
*/

#pragma once

#include <iostream>

namespace My_memory{
template<class T>
class My_unique_ptr;

template<class T>
My_unique_ptr<T> make_unique(T creator){
    My_unique_ptr<T> part(new T(creator));
    return part;
}

template<class T>
class My_unique_ptr{
public:
    My_unique_ptr() = delete;
    My_unique_ptr(T* other);
    My_unique_ptr(const My_unique_ptr& other) = delete;
    My_unique_ptr(My_unique_ptr&& other) explicit;
    ~My_unique_ptr();

    void operator=(const My_unique_ptr& other) = delete;
    const T operator*() const;
    const T operator->() const;

    T* get() const;
private:
    // 底层维护的指针
    T* ptr;
};

template <class T>
My_unique_ptr<T>::My_unique_ptr(T *other){
    if(other == nullptr){
        throw std::logic_error("pointing a nullptr");
    }
    else{
        ptr = other;
    }
}

template <class T>
My_unique_ptr<T>::My_unique_ptr(My_unique_ptr &&other){
    if(this->ptr == other.get()){
        throw std::logic_error("Trying to replicate itself");
        return;
    }
    ptr = other.get();
    
}

template <class T>
My_unique_ptr<T>::~My_unique_ptr()
{
    if(ptr != nullptr){
        delete ptr;
        ptr = nullptr;
    }

}

template<class T>
const T My_unique_ptr<T>::operator*() const{
    if(this->get() == nullptr){
            throw std::logic_error("Dereferencing an unallocated My_unique_ptr");
        return T();
    }
    return *(this->get());

}

template <class T>
const T My_unique_ptr<T>::operator->() const{
    if(this->ptr != nullptr){
        return ptr;
    }
    throw std::logic_error("Accessing member of an unallocated My_unique_ptr");
}

template<class T>
T* My_unique_ptr<T>::get() const{
    return this->ptr;
}

}

试了下,至少逻辑都没啥问题,大致做到了我想要的结果,只是对比起std,还差挺多,理解了原理应该就行了,附上一段简单的测试代码:

#include "my_unique_ptr.h"
#include <iostream>

using namespace My_memory;

int main(){
    My_unique_ptr<int> ptr{new int(10)};
    std::cout << *ptr;

    // 会有重复delete的问题,但我没有去解决这个bug
    // My_unique_ptr<int> ptr2(std::move(ptr));

    My_unique_ptr<int> ptr3 = My_memory::make_unique<int>(10);

    // 会正常报错
    // My_unique_ptr<int> ptr4(ptr3);
}

计数指针shared_ptr

之前的unique_ptr是独占指针,只允许一个指针指向该地址,并且它是禁用拷贝的,而shared_ptr正好相反,它的内部维护了一个计数器,与其类对象指的内存相关联
在copy之后,计数器+1,销毁后计数器-1,当计数器为0的时候,shared_ptr对象销毁。
shared_pr依旧使用get()获取地址,除此之外,使用use_count()获取其底部引用计数器的数值。

#include <iostream>
#include <memory>
using namespace std;
void function(shared_ptr<int> ptr){
    cout << "进入函数:" << ptr.use_count() << endl;
}

int main(){
    shared_ptr<int> ptr{new int(10)};

    cout << "未进入函数:" << ptr.use_count() << endl;

    function(ptr);

    cout << "退出函数:" << ptr.use_count();
}

通过这段代码我们能够看到shared_ptr的计时器的变化,可以帮助理解shared_ptr的原理。

unique_ptr与shared_ptr

不能将shared_ptr转换为unique_ptr,但是unique_ptr可以通过std::move转换为shared_ptr
shared_ptr可以是空构造,但是unique_ptr不行

#include <iostream>
#include <memory>
using namespace std;

unique_ptr<int> function(){
	unique_ptr<int> ptr = std::make_unique<int>(10);
	return ptr;
}

int main(){
	std::shared_ptr<int> sh_ptr = function();
}

weak_ptr弱指针(弱引用)

和unique_ptr和shared_ptr不一样,weak_ptr不拥有所有权,也没法调用成员访问符“->”和解引用符“*”
但是weak_ptr和shared_ptr紧密关联,我们先说说shared_ptr所存在的问题吧:循环引用

循环引用

什么是循环引用呢?就是对象A其中的成员指针指向了对象B,同时,对象B中的指针又指向了对象A,两者的指针都是shared_ptr,它们都需要等待引用数为0才会销毁。
但是,它们都在相互等待,也就出现了死锁
weak_ptr就是为了解决这个场景提出的。

#include <memory>
#include <iostream>
using namespace std;

int main(){
	shared_ptr<int> s_ptr = std::make_shared<int>(10);
    weak_ptr<int> w_ptr(s_ptr);

    // 可以发现,我们通过w_ptr可以正常调用s_ptr
    // 但是count不会增加
    cout << "w_ptr.use_count = " << w_ptr.use_count() << endl;
    cout << "s_ptr.use_count = " << s_ptr.use_count() << endl;
    
    // weak_ptr使用lock()可以转换为shared_ptr
    shared_ptr<int> ch_ptr = w_ptr.lock();
    // 在转换后,shared_ptr的引用计数器增加
    cout << "s_ptr.count = " << ch_ptr.use_count();
}

上述代码中就涵盖对weak_ptr的使用:

  • 创建weak_ptr的时候需要使用一个已存在的shared_ptr进行初始化,初始化后weak_ptr指向这个shared_ptr所管理的对象,但是shared_ptr的计数器不会增加,因为weak_ptr不会对其指向的内容进行强引用
  • weak_ptr使用了lock()之后,就会生成一个shared_ptr,就是其指向的shared_ptr,shared_ptr其中的计数器也会+1

接下来看看这段代码:

#include <memory>
#include <iostream>
using namespace std;

int main(){
    // shared_ptr<int> s_ptr = make_shared<int>(10);
    weak_ptr<int> w_ptr;
    {
        shared_ptr<int> s_ptr = make_shared<int>(10);
        w_ptr = s_ptr;
        cout << "s_ptr.get = " << s_ptr.get() << endl;
    }
    shared_ptr<int> s_ptr = w_ptr.lock();
    cout << "other s_ptr.get = " << s_ptr.get();
}

当weak_ptr指向的shared_ptr所指向的对象被销毁的时候(这意味着管理该对象的shared_ptr的引用计数为0),weak_ptr会置为nullptr,这是weak_ptr的一个重要特性,用于避免内存泄漏。

  • 27
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

默示MoS

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

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

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

打赏作者

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

抵扣说明:

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

余额充值