C++智能指针

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

posted @ 2024-02-21 21:07  贝壳里的星海  阅读(32)  评论(0编辑  收藏  举报