首页 > 编程语言 >C++智能指针

C++智能指针

时间:2024-02-21 21:11:10浏览次数:37  
标签:std cout int C++ 智能 shared ptr 指针

C++智能指针

目录

在C++中,内存的分配和释放都是由开发者手动实现的。这种方式虽然很灵活,但也十分容易出错,比如忘记释放内存或释放了已经释放的内存等。

动态内存的管理是通过一对运算符来完成的:

  • new: 在动态内存中为对象分配空间,并且返回一个指向该对象的指针,我们可以选择对对象进行初始化
  • delete: 接受一个动态对象的指针销毁该对象,并且释放与之关联的内存

动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的:

  • 有时我们会忘记释放内存,在这种情况下会产生内存泄漏
  • 有时在尚有指针引用内存的情况下我们就释放了它,这种情况下会产生引用非法内存的指针

为了避免这些问题,C++引入了智能指针这一概念。智能指针是一种类,它在析构时自动释放所管理的对象所占用的内存。这样,程序员就不需要手动管理内存,减少了出错的可能性。

C++中有三种智能指针:unique_ptrshared_ptrweak_ptr

unique_ptr

unique_ptr是一个独占式的指针对象,不能共享所有权。也不允许复制拷贝,在任何时间、资源只能被一个指针占有

unique_ptr被销毁时,或者离开作用域,它所管理的对象的内存也会被自动释放。

简单示例

std::unique_ptr<int>p1(new int(5));
std::unique_ptr<int>p2=p1;				// 编译会出错
std::unique_ptr<int>p3=std::move(p1);	// 转移所有权, 现在那块内存归p3所有, p1成为无效的针.
p3.reset();		//释放内存.
p1.reset();		//无效

构造方法

#include <iostream>
#include <memory>
 
int main () {
  std::default_delete<int> d;
  std::unique_ptr<int> u1;						// u1 指针		
  std::unique_ptr<int> u2 (nullptr);			// u2 指针
  std::unique_ptr<int> u3 (new int);            
  std::unique_ptr<int> u4 (new int, d);
  std::unique_ptr<int> u5 (new int, std::default_delete<int>());
  std::unique_ptr<int> u6 (std::move(u5));
  std::unique_ptr<int> u7 (std::move(u6));
  std::unique_ptr<int> u8 (std::auto_ptr<int>(new int));
 
  std::cout << "u1: " << (u1?"not null":"null") << '\n';
  std::cout << "u2: " << (u2?"not null":"null") << '\n';
  std::cout << "u3: " << (u3?"not null":"null") << '\n';
  std::cout << "u4: " << (u4?"not null":"null") << '\n';
  std::cout << "u5: " << (u5?"not null":"null") << '\n';
  std::cout << "u6: " << (u6?"not null":"null") << '\n';
  std::cout << "u7: " << (u7?"not null":"null") << '\n';
  std::cout << "u8: " << (u8?"not null":"null") << '\n';
 
  return 0;
}
// --------------------------------------------------------------------------
u1: null
u2: null
u3: not null
u4: not null
u5: null
u6: null
u7: not null
u8: not null

释放和重置

释放 std::unique_ptr::release
释放并不会销毁其指向的对象,而且将其指向的对象释放出去

重置 std::unique_ptr::reset
#include <iostream>
#include <memory>

void Test1(){
  std::unique_ptr<int> auto_pointer (new int);			// 初始化
  int * manual_pointer;
  *auto_pointer=10;
  manual_pointer = auto_pointer.release();              // 释放
  // (auto_pointer is now empty)
  std::cout << "manual_pointer points to " << *manual_pointer << '\n';
  std::cout << "auto_pointer :" <<*auto_pointer << '\n';
  delete manual_pointer;
    
}
int main () {
	Test1();
  	return 0;
}
// ------------------------------------------------------------------
manual_pointer points to 10
auto_pointer :
#include <iostream>
#include <memory>

void Test2(){
  std::unique_ptr<int> up;  // empty
  up.reset (new int);       // 重置
  *up=5;
  std::cout << "*up:" << *up << '\n';
  up.reset (new int);       // 重置
  *up=10;
  std::cout << "*up:" << *up << '\n';
  up.reset();               // deletes managed object 
}
int main () {
	Test2();
  	return 0;
}
// -------------------------------------------------------------------
*up:5
*up:10

参考 https://blog.csdn.net/fuhanghang/article/details/113928128z

#include <iostream>
#include <memory>

using namespace std;

int main() {
    // 使用unique_ptr管理int类型的对象
    unique_ptr<int> up1(new int(10));
    cout << "up1: " << *up1 << std::endl;

    // 使用make_unique函数创建unique_ptr对象
    auto up2 = make_unique<int>(20);
    cout << "up2: " << *up2 << std::endl;

    // unique_ptr可以通过std::move()转移所有权
    unique_ptr<int> up3 = std::move(up1);
    cout << "up3: " << *up3 << std::endl;
    
    return 0;
}

shared_ptr

shared_ptr是一个共享所有权的智能指针,可以有多个shared_ptr指向同一个对象。

每当一个shared_ptr被销毁时,它所管理的对象的引用计数会减1。当引用计数为0时,对象的内存才会被自动释放。

构造方法

#include <iostream>
#include <memory>

///1.构造函数初始化

int main(){
    std::shared_ptr<int>  p0(nullptr);
    std::cout << "p0.use_count=" << p0.use_count() << '\n'; //空指针 计数=0

    //构造函数初始化,指向一个存有5这个int类型数据的堆内存空间
    std::shared_ptr<int> p1(new int(5));
    std::cout << "p1=" << *p1  << '\n';						    //p1=5
    std::cout << "p1.use_count=" << p1.use_count() << '\n';     //1

    std::shared_ptr<int> p2(p1);	//p1和p2都指向那个存有int型5的堆内存空间,堆内存的引用次数会加1
    std::cout << "p1.use_count=" << p1.use_count() << '\n';	    //2
    std::cout << "p2.use_count=" << p2.use_count() << '\n';	    //2

    std::shared_ptr<int> p3=p0;	    //p0为空,则p3也为空,其引用计数依然为0
    std::cout << "p3.use_count=" << p3.use_count() << '\n';     //0

    //补充
    //可以把原始指针传参构造shared_ptr对象,此时原始指针没有new或没有初始化赋值,use_count都是1
    int *p11 = new int;   // 原始指针 计数为1
    //如果没有=new int,下面p12.use_count还是1,但执行打印时会crash
    std::shared_ptr<int> p12(p11);
    std::cout << "p12.use_count=",p12.use_count() << '\n';	//1
    std::cout << "*p12=" << *p12 << '\n';//*p12=1345903632(原始指针new了,但没有初始化,则打印未知值)
}

// ------------------------------------------------------------------------
p0.use_count=0
p1=5
p1.use_count=1
p1.use_count=2
p2.use_count=2
p3.use_count=0
p12.use_count=*p12=42083472

//使用make_shared.  更高效
auto p1 = std::make_shared<int>(10);
auto p2 = std::make_shared<string>(10,"s");
auto p3 = std::make_shared<Struct>();

常用函数

get()函数,表示返回当前存储的原始指针

shared_ptr<T> ptr(new T());
T *p = ptr.get(); // 获得传统 C 指针

use_count()函数,表示当前引用计数
shared_ptr<T> a(new T());
a.use_count(); //获取当前的引用计数

reset()函数,表示重置当前存储的指针
shared_ptr<T> a(new T());
a.reset(); // 此后 a 原先所指的对象会被销毁,并且 a 会变成 NULL

构造重置和析构

#include <iostream>
#include <memory>
class book
{
public:
    book(int v) {					//构造函数
        value = v;
        std::cout << "cons book value=" <<value<< std::endl;
    }
    ~book() {//析构函数
        std::cout << "desc book value=" <<value<< std::endl;
    }
    int value;
};
int main()
{
    std::shared_ptr<book> b1(new book(100));	//cons book value=100
    std::shared_ptr<book> b2(b1);				//只增加了引用计数,没有新增构造book,use_count=2
    b2.reset(new book(200));			//cons book value=200(新构造book200,原book100计数变为1)
    b1.reset(new book(300));			//cons book value=300   
    desc book value=100(新构造book300,原book100引用计数变为0,执行析构)
	return 0;
}

获得原始指针

std::shared_ptr<int> p10(new int(300));
int *pn = p10.get();
std::cout << "pn=",*pn  << '\n';	//pn=300
struct ClassWrapper {

    ClassWrapper() {
        cout << "construct" << endl;
        data = new int[10];
    }

    ~ClassWrapper() {
        cout << "deconstruct" << endl;
        if (data != nullptr) {
            delete[] data;
        }
    }

    void Print() {
        cout << "print" << endl;
    }

    int* data;
};

void Func(std::shared_ptr<ClassWrapper> ptr) {
    ptr->Print();
}

int main() {
    auto smart_ptr = std::make_shared<ClassWrapper>();
    auto ptr2 = smart_ptr; // 引用计数+1
    ptr2->Print();
    Func(smart_ptr); // 引用计数+1
    smart_ptr->Print();
    ClassWrapper *p = smart_ptr.get(); // 可以通过get获取裸指针
    p->Print();
    return 0;
}

注意事项

  • 不能使用原始指针初始化多个shared_ptr,会出现double_free导致程序崩溃
  • 不允许以暴露裸漏的指针进行赋值
  • shared_ptr不是线程安全;

​ 在多线程下,不能保证new出来一个对象一定能被放入shared_ptr中,也不能保证智能指针管理的引用计数的正确性;

  • 通过shared_from_this()返回this指针,不要把this指针作为shared_ptr返回出来,因为this指针本质就是裸指针,通过this返回可能 会导致重复析构,不能把this指针交给智能指针管理。
  • 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。

weak_ptr

weak_ptr是一个弱引用的智能指针,它与shared_ptr搭配使用。

借助 weak_ptr 类型指针可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针。

weak_ptr不会增加所管理的对象的引用计数,因此它不会影响对象的生命周期。

可以通过weak_ptrlock()成员函数来获取一个指向所管理的对象的shared_ptr

weak_ptr是用来监视shared_ptr的生命周期,它不管理shared_ptr内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。

构造方法

#include <iostream>
using namespace std;

int main()
{
	auto sp = make_shared<int>(100); // 强引用计数从0变1
	
	weak_ptr<int> wp(sp); // wp弱共享sp,强引用计数仍为1,弱引用计数从0变1

	return 0;
}

常用函数

.use_count() 获取当前'强引用'所管理的资源的引用计数的个数

.expired() 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false

.reset() 在弱引用weak_ptr中,将该弱引用指针设置为空nullptr,不影响指向该对象的强引用数量

.lock() 判断weak_ptr所指向的shared_ptr对象是否存在****。若存在,则这个lock方法会返回一个指向该对象的shared_ptr指针;若它所指向的这个shared_ptr对象不存在,则lock()函数会返回一个空的shared_ptr

weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数

#include <memory>
#include <iostream>

int main(){

    auto pi1 = std::make_shared<int>(111);
    auto pi2(pi1);//pi2是一个shared_ptr
    std::weak_ptr<int> piw(pi1);
    int n = piw.use_count();
    std::cout << "n = " << n << std::endl;
    pi1.reset();
    pi2.reset();
    if (piw.expired()) {
        std::cout << "weak_ptr is release" << std::endl;
    }

    auto ps1 = std::make_shared<int>(42);
    std::weak_ptr<int> psw;
    psw = ps1;                  //用shared_ptr给weak_ptr赋值
    if (!psw.expired()) {
        auto ps2 = psw.lock();  //.lock()方法返回一个shared_ptr,并且此时强引用计数为2
        if (ps2) {
            //ps2非空,表明psw所指向的shared_ptr所管理的对象此时没有expire
            std::cout << "ps2 is exist" << std::endl;
        }
        else {
            std::cout << "ps2 not exist" << std::endl;
        }
    }
    return 0;
}
// ---------------------------------------------------------------------------------
n = 2
weak_ptr is release
ps2 is exist
#include <memory>
#include <iostream>

int main(){

    std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);               //创建一个智能指针
    std::cout << "sh_ptr.use_count()=" << sh_ptr.use_count() << "\n";      //sh_ptr.use_count()=1

    std::weak_ptr<int> wp(sh_ptr);          //构造 weak_ptr,不会增加智能指针的引用计数
    std::cout << "sh_ptr.use_count()=" << sh_ptr.use_count() << "\n"; //sh_ptr.use_count()=1
    std::cout << "wp.use_count()=" << wp.use_count() << "\n";         // wp.use_count() =1

    if(!wp.expired()){ // 检查sh_ptr是否还有效
            std::shared_ptr<int> sh_ptr2 = wp.lock();           //使用 weak_ptr 构造一个智能指针,引用计数+1
            *sh_ptr = 100;
            std::cout << "wp.use_count()=" << wp.use_count() << "\n";    // wp.use_count() =2
        }
    return 0;
}
// ----------------------------------------------------------------------
sh_ptr.use_count()=1
sh_ptr.use_count()=1
wp.use_count()=1
wp.use_count()=2

解决循环引用

weak_ptr的一个作用是解决share_ptr的循环引用问题

#include <memory>
#include <iostream>

class BB;

class AA
{
public:
    AA() {  std::cout << "AA::AA() called\n"; }
    ~AA() { std::cout << "AA::~AA() called\n"; }
    std::shared_ptr<BB> m_bb_ptr;   //正确用法是使用weak_ptr,可以正常析构
};

class BB
{
 public:
    BB() { std::cout <<"BB::BB() called\n" ; }
    ~BB() { std::cout <<"BB::~BB() called\n" ; }
    std::shared_ptr<AA> m_aa_ptr;   //正确用法是使用weak_ptr,可以正常析构
};

int main(){
    std::shared_ptr<AA> ptr_a(new AA);
    std::shared_ptr<BB> ptr_b(new BB);

    std::cout <<  "ptr_a use_count:" << ptr_a.use_count()  << "\n";
    std::cout <<  "ptr_b use_count:" << ptr_b.use_count()  << "\n";
    
    //下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构
    ptr_a->m_bb_ptr = ptr_b;
    ptr_b->m_aa_ptr = ptr_a;
    std::cout << "ptr_a use_count:" << ptr_a.use_count()  << "\n";
    std::cout << "ptr_b use_count:" << ptr_b.use_count()  << "\n";

    return 0;
}
// --------------------------------------------------------------------------------------
AA::AA() called
BB::BB() called
ptr_a use_count:1
ptr_b use_count:1
ptr_a use_count:2
ptr_b use_count:2

循环引用导致ptr_a和ptr_b的引用计数都为2

在离开作用域之后,ptr_a和ptr_b的引用计数只减为1,而没有减为0,导致两个指针都不会被析构,产生内存泄漏。

#include <memory>
#include <iostream>

class BB;

class AA
{
public:
    AA() {  std::cout << "AA::AA() called\n"; }
    ~AA() { std::cout << "AA::~AA() called\n"; }
    std::weak_ptr<BB> m_bb_ptr;   //正确用法是使用weak_ptr,可以正常析构
};

class BB
{
 public:
    BB() { std::cout <<"BB::BB() called\n" ; }
    ~BB() { std::cout <<"BB::~BB() called\n" ; }
    std::weak_ptr<AA> m_aa_ptr;   //正确用法是使用weak_ptr,可以正常析构
};

int main(){
    std::shared_ptr<AA> ptr_a(new AA);
    std::shared_ptr<BB> ptr_b(new BB);

    std::cout <<  "ptr_a use_count:" << ptr_a.use_count()  << "\n";
    std::cout <<  "ptr_b use_count:" << ptr_b.use_count()  << "\n";
    
    //下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构
    ptr_a->m_bb_ptr = ptr_b;
    ptr_b->m_aa_ptr = ptr_a;
    std::cout << "ptr_a use_count:" << ptr_a.use_count()  << "\n";
    std::cout << "ptr_b use_count:" << ptr_b.use_count()  << "\n";

    return 0;
}
// ---------------------------------------------------------------------
AA::AA() called
BB::BB() called
ptr_a use_count:1
ptr_b use_count:1
ptr_a use_count:1
ptr_b use_count:1
BB::~BB() called
AA::~AA() called

参考资料

万字长文详解C++智能指针

C++智能指针shared_ptr用法

C++多线程与共享指针

https://juejin.cn/post/7078508447053905933

https://juejin.cn/post/7243082572538904613?searchId=20231228173634B4D68943E877C367A478

标签:std,cout,int,C++,智能,shared,ptr,指针
From: https://www.cnblogs.com/tian777/p/18026230

相关文章

  • C++ 第一节课 名字空间 ,输入输出函数,和 C 语言的区别
    #include<iostream>//#include头文件,C++标准库的头文件都不带.h(.h是C库头文件添加的)#include<cstdio>#include<cstring>usingnamespacestd;//namespace命名空间为了防止变量名字冲突//命名空间中定义自己的变量或函数或类,都是独立的//所有的命名空......
  • golang指针和结构体
    指针指针操作指针包括指针地址、指针类型和指针取值&:&符号放在变量前面进行取地址操作**:*放在变量前面根据地址进行取值指针地址:funcmain(){ varaint=1 //a的值是1--类型是int--,地址是0xc0000120c0,&是地址符号 fmt.Printf("a的值是%v--类型是%T--,地......
  • 49. 字母异位词分组c++
    刷力扣还有点不太习惯,主要是C++只学了皮毛。看了官方活用map就是好啊。把字母都排好序然后判断就好了。map<string,vector<string>>m;for(inti=0;i<strs.size();i++){stringtem=strs[i];sort(tem.begin(),tem.end());......
  • 【C++】编写一个具有老式风格接口的函数,其原型如下:int reduce(long arr[], int n)。实
    #include<iostream>#include<string>usingnamespacestd;intreduce(longarr[],intn){sort(arr,arr+n);autostr=unique(arr,arr+n);returnstr-arr;}intmain(){longarr[10]={15,8,5,6,11,11,6,6,198,50};......
  • c++类开发的第三篇(讲明白友元函数和this指针)
    friend_function成员变量和函数的存储c++实现了封装,数据和处理数据的操作(函数)是分开存储的。c++中的非静态数据成员直接内含在类对象中,就像c语言的struct一样。成员函数并不会出现在对象中,而是作为类的一部分存储在代码段中,需要通过对象或对象指针进行调用。成员函数可......
  • Topaz Video AI:一键提升视频品质,智能重塑影像魅力 mac/win版
    TopazVideoAI是一款革命性的视频智能处理软件,它利用先进的机器学习和人工智能技术,为视频创作者提供了前所未有的视频增强和修复功能。无论您是专业视频编辑师、摄影师,还是热爱视频创作的爱好者,TopazVideoAI都能帮助您轻松提升视频质量,创造出更加生动、引人入胜的影像作品。→......
  • C++开发基础知识(修改)
    2024-01-0820:13星期一博客内容来自相关书籍和网站内容总结,仅供个人参考使用:笔者@StuBoo使用目录快速转到技术面试问题汇总、算法笔记1.C++语言基础1.1语言特性面向对象编程(OOP):C++支持面向对象编程,包括封装、继承和多态。通过类和对象,可以将数据和方法组织成单个单元,......
  • c++ 2 字母异位词
    //字母异位词是由重新排列源单词的所有字母得到的一个新单词。//示例1:////输入:strs=["eat","tea","tan","ate","nat","bat"]//输出:[["bat"],["nat","tan"],["ate","eat",&q......
  • 1 c++算法题解析-两个数之和
    //给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回它们的数组下标。//你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。//你可以按任意顺序返回答案。//示例1:////输入:nums=[2,7,......
  • C++限制函数最大执行时间
    背景C++调用某些硬件操作(如TPU推理)可能存在超时风险,需要限制函数的执行时间。思考异步执行免不了开线程,如何限制join的最大时间是关键。设计如下函数:boolInfer(uinttimeout_ms)根据输入的timeout_ms参数,按时完成返回true超时返回false。实现使用std::mutex配合std::con......