智能指针学习笔记

目录

1 智能指针简介

关于智能指针

概述

2 独占指针:unique_ptr

3 unique_ptr与函数调用

4 计数指针 share_ptr

5 shared_ptr与函数

6 shared_ptr与unique_ptr

7 weak_ptr


1 智能指针简介

关于智能指针

  • 在C++ 11中引入智能指针的概念,使得C++程序员不需要手动释放内存
  • 智能指针的分类:unique_ptr, shared_ptr, weak_ptr 
  • 注意:auto_ptr已经被抛弃

概述

C++指针包含两种

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

智能指针和原始指针的关系

  • 并不是所有的指针可以封装为智能指针,很多时候原始指针更方便

  • 各种指针中,最常用的是原始指针,其次是unique_ptr和shared_ptr

  • weak_ptr是share_ptr的一种补充,应用场景少

接下来思考一个问题,为什么有了智能指针,还需要rust呢?

  • 因为智能指针只解决了一部分问题,即独占/共享所有权指针的释放和传输
  • 智能指针没有从根本上解决C++内存安全的问题,不加以注意依然造成内存安全问题,而rust解决的问题更广泛一些。

2 独占指针:unique_ptr

特点:

  • 在任何给定的时刻,只能有一个指针管理内存
  • 当指针超出作用域时,内存将自动释放
  • 该类型指针不可copy,只可以move

三种创建方式:

  • 通过已有指针创建:需要将指针置为空,并销毁,不然不满足独占作用
  • 通过new创建:同上
  • 通过make_unique创建:推荐使用

unique_ptr可以通过get()获取地址,另外可以通过->调用成员函数,通过*进行解引用。

//cat.h
#ifndef CAT_H
#define CAT_H
#include <string>
#include <iostream>
#include "cat.h"
class Cat
{
public:
    Cat(std::string name);
    Cat() = default;
    ~Cat();
    void cat_info() const
    {
        std::cout << "cat info name: " << name << std::endl;
    }
    std::string get_name() const
    {
        return name;
    }
    void set_cat_name(const std::string& name)
    {
        this->name = name;
    }
private:
    std::string name{ "Mini" };
};
#endif
//cat.cpp
#include "cat.h"

Cat::Cat(std::string name) : name(name)
{
    std::cout << "Constructor of Cat :" << name << std::endl;
}
Cat::~Cat() 
{
    std::cout << "Destructor of Cat :" << name << std::endl;
}
//main.cpp
#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

int main(int argc, char *argv[])
{
    Cat c1("OK");
    c1.cat_info();
    {
        Cat c1("OK");
        c1.cat_info();
    }
    cout << "-------------yz----------" << endl;
    return 0;
}

 

//没有delete由new创建的指针
#include<iostream>
#include<memory>
#inlclude"cat.h"
using namespace std;

int main()
{
    Cat *cp1 = new Cat("");
    cp1->cat_info();
    {
        Cat *cp1 = new Cat("yy");
        cp1->cat_info();
    }
    cout<< "-------------yz----------";
    return 0;
}

以上代码是有问题的,我们没有去释放申请的内存,是不会调用析构函数,内存得不到释放。另外当我们delete两次指针的时候也会出现错误。

因此引入unique_ptr智能指针解决上述问题

代码实现如下

#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

int main(int argc, char *argv[])
{
    //创建unique_ptr方式1
    Cat *cp1 = new Cat("yz");
    std::unique_ptr<Cat> u_cp1(cp1);
    u_cp1->cat_info();
    delete cp1;
    cp1 = nullptr;
    
    cout << "-------------yz----------" << endl;

    //创建unique_ptr方式2
    std::unique_ptr<Cat> u_cp2(new Cat("gg"));
    u_cp2->cat_info();
    cout << "-------------yz----------" << endl;

    //创建unique_ptr方式3
    std::unique_ptr<Cat> u_cp3 = make_unique<Cat>();
    u_cp3->cat_info();
    return 0;

}

但是需要注意的是,方式一好像在最新的g++编译器上不需要delete和置空操作。如下为我的上述代码的输出。

Constructor of Cat :yz
cat info name: yz
Destructor of Cat :yz
-------------yz----------
Constructor of Cat :gg
cat info name: gg
-------------yz----------
cat info name: Mini
Destructor of Cat :Mini
Destructor of Cat :gg
Destructor of Cat :free(): double free detected in tcache 2
Aborted (core dumped)

如果我将delete操作和置空操作删除,例如

#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

int main(int argc, char *argv[])
{
    //创建unique_ptr方式1
    Cat *cp1 = new Cat("yz");
    std::unique_ptr<Cat> u_cp1(cp1);
    u_cp1->cat_info();
    
    cout << "-------------yz----------" << endl;

    //创建unique_ptr方式2
    std::unique_ptr<Cat> u_cp2(new Cat("gg"));
    u_cp2->cat_info();
    cout << "-------------yz----------" << endl;

    //创建unique_ptr方式3
    std::unique_ptr<Cat> u_cp3 = make_unique<Cat>();
    u_cp3->cat_info();
    return 0;

}

输出为

Constructor of Cat :yz
cat info name: yz
-------------yz----------
Constructor of Cat :gg
cat info name: gg
-------------yz----------
cat info name: Mini
Destructor of Cat :Mini
Destructor of Cat :gg
Destructor of Cat :yz

下面介绍解引用和取地址操作,以方式为例

#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

int main(int argc, char *argv[])
{
    //创建unique_ptr方式3
    std::unique_ptr<int> u_cp3 = make_unique<int>(200);
    cout<<"* u_cp3 = "<<* u_cp3<<endl;
    cout<<"get adress"<<u_cp3.get()<<endl;
    return 0;

}

输出为

* u_cp3 = 200
get adress0x55e978458e70

3 unique_ptr与函数调用

  • unique_ptr是不可Copy,只可以Move
  • 在做函数参数或返回值中一定要注意所有权

注意事项:

Passing by value

  • 需要注意std::move来转移内存拥有权
  • 如果参数之间传入std:make_unique语句 自动转换为move
#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

void Passing_by_value( std::unique_ptr<Cat> c){
    c->cat_info();
}

int main(int argc, char *argv[])
{
    //创建unique_ptr方式3
    std::unique_ptr<Cat> u_cp1 = make_unique<Cat>("haha");
    Passing_by_value(std::move(u_cp1));//需要注意std::move来转移内存拥有权,否则报错。另外move后也不能再使用u_cp1
    Passing_by_value(std::make_unique<Cat>("xixi"));
    
    return 0;

}

从下面输出结果看到,在move操作转移所有权后会调用Passing_by_value函数和析构函数,说明智能指针不再可用。

Constructor of Cat :haha
cat info name: haha
Destructor of Cat :haha
Constructor of Cat :xixi
cat info name: xixi
Destructor of Cat :xixi

Passing by reference

  • 如果设置参数为const则不能改变指向,比如说reset()
  • reset()方法为智能指针清空方法。当传常引用时,不可以用reset()清空智能指针。
#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

void Passing_by_ref( const std::unique_ptr<Cat> &c){
    c->set_cat_name("ee");
    c->cat_info();
}

int main(int argc, char *argv[])
{
    //创建unique_ptr方式3
    std::unique_ptr<Cat> u_cp1 = make_unique<Cat>("haha");
    Passing_by_ref(u_cp1);//需要注意std::move来转移内存拥有权,否则报错。另外move后也不能再使用u_cp1
    u_cp1->cat_info();
    
    return 0;

}

由下列输出结果可得,传引用可以修改变量值。

Constructor of Cat :haha
cat info name: ee
cat info name: ee
Destructor of Cat :ee

Return by value

  • 指向一个local object
  • 可以作为链式函数
#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

std::unique_ptr<Cat> get_unique_ptr()//return by value
{
    std::unique_ptr<Cat> p_dog = std::make_unique<Cat>("dog");
    cout<<"unique_ptr adresss"<<p_dog.get()<<endl;
    cout<<"unique_ptr adresss"<<&p_dog<<endl;
    return p_dog;
}

int main(int argc, char *argv[])
{
   
    get_unique_ptr()->cat_info();//链式调用
    
    return 0;

}

输出为

Constructor of Cat :dog
unique_ptr adresss0x55a6558e1e70
unique_ptr adresss0x7ffeb6c9ae70
cat info name: dog
Destructor of Cat :dog

4 计数指针 share_ptr

  • 计数指针 share_ptr又称为共享指针
  • 与unique_ptr不同的是可以共享数据
  • share_ptr创建了一个计数器与类对象所指的内存相关联
  • Copy则计数加1,销毁则减1
  • api为use_count(),查询引用计数
#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;


int main(int argc, char *argv[])
{
   
    std::shared_ptr<Cat> p1 = make_shared<Cat>("aa");
    p1->cat_info();
    cout<<"p1 count: "<<p1.use_count()<<endl;
    std::shared_ptr<Cat> p2 = p1;
    cout<<"p1 count: "<<p1.use_count()<<endl;
    cout<<"p2 count: "<<p2.use_count()<<endl;
    p1=nullptr;
    cout<<"p1 count: "<<p1.use_count()<<endl;
    cout<<"p2 count: "<<p2.use_count()<<endl;
    return 0;

}

 输出为

Constructor of Cat :aa
cat info name: aa
p1 count: 1
p1 count: 2
p2 count: 2
p1 count: 0
p2 count: 1
Destructor of Cat :aa

值得注意的是,无论引用计数为多少,数据只有一套。无论引用计数为多少,程序运行结束时只会调用一次析构函数。

5 shared_ptr与函数

  • shared_ptr passed by value :copy时引用计数加1
#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

void pass_by_value(std::shared_ptr<Cat> c){
    c->cat_info();
    cout<<"count: "<<c.use_count()<<endl;
}

int main(int argc, char *argv[])
{
   
    std::shared_ptr<Cat> p1 = make_shared<Cat>("aa");
    p1->cat_info();
    pass_by_value(p1);
    cout<<"p1 count: "<<p1.use_count()<<endl;
    
    return 0;

}

由下面可得,当进入函数体时引用计数加1,离开函数体减1。

Constructor of Cat :aa
cat info name: aa
cat info name: aa
count: 2
p1 count: 1
Destructor of Cat :aa

  • shared_ptr passed by ref:const表示不可以改变指向

类似与unique_ptr

  • return by value:链式调用

l类似与unique_ptr

6 shared_ptr与unique_ptr

  • 不能将shared_ptr转换为unique_ptr
  • unique_ptr可以转换为shared_ptr:通过std::move转换
  • 将你的函数返回unique_ptr是一种常见的设计模式,这样可以提高代码的复用性,你可以随时改变为shared_ptr
#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;

std::unique_ptr<Cat> pass_by_value(){
    std::unique_ptr<Cat> c = make_unique<Cat>("cc");
    return c;
}

int main(int argc, char *argv[])
{
    std::unique_ptr<Cat> p0 = make_unique<Cat>("bb");
    std::shared_ptr<Cat> p1 = std::move(p0);//unique_ptr转换为shared_ptr
    p1->cat_info();
    cout<<"p1 count: "<<p1.use_count()<<endl;
    
    
    std::shared_ptr<Cat> p3 = pass_by_value();//直接用shared_ptr接收unique_ptr
    cout<<"p3 count"<<p3.use_count()<<endl;
    return 0;

}

输出为

Constructor of Cat :bb
cat info name: bb
p1 count: 1
Constructor of Cat :cc
p3 count1
Destructor of Cat :cc
Destructor of Cat :bb

7 weak_ptr

  • weak_ptr并不拥有所有权
  • 不能调用->和解引用*

weak_ptr为什么存在呢?

A类中有一个需求需要存储其他A类对象的信息,如果使用shared_ptr,那么在销毁时会遇到循环依赖问题,所以我们这里需要用一个不需要拥有权的指针来标记同类对象。

weak_ptr可以通过lock()函数来提升为shared_ptr(类型转换)

例1

#include<iostream>
#include<memory>
#include "cat.h"
#include "cat.cpp"
using namespace std;


int main(int argc, char *argv[])
{
    std::shared_ptr<Cat> p0 = make_shared<Cat>("bb");
    std::weak_ptr<Cat> p1 = p0;
    cout<<"p0 use_count: "<<p0.use_count()<<endl;

    std::shared_ptr<Cat> p2 = p1.lock();
    cout<<"p0 use_count: "<<p0.use_count()<<endl;

    return 0;

}

输出如下,可以看出weak_ptr不会增加引用计数,将weak_ptr使用lock()函数转换后可以增加引用计数

Constructor of Cat :bb
p0 use_count: 1
p0 use_count: 2
Destructor of Cat :bb

解决循环依赖问题

更改cat.h代码如下

#ifndef CAT_H
#define CAT_H
#include <string>
#include <iostream>
#include "cat.h"
#include<memory>
class Cat
{
public:
    Cat(std::string name);
    Cat() = default;
    ~Cat();
    void cat_info() const
    {
        std::cout << "cat info name: " << name << std::endl;
    }
    std::string get_name() const
    {
        return name;
    }
    void set_cat_name(const std::string& name)
    {
        this->name = name;
    }
    void set_friend(std::shared_ptr<Cat> c){
        m_friend = c;
    }
private:
    std::string name{ "Mini" };
    std::shared_ptr<Cat> m_friend;
};
#endif

 此时就需要用weak_ptr来解决上图中的问题了,我们需要修改cat.h代码,如下图

 此时再去运行main函数,就发现问题已经解决了

 

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@Finish_all

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

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

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

打赏作者

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

抵扣说明:

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

余额充值