C++的智能指针

智能指针

智能指针是用于管理动态分配内存的一种工具,它们可以帮助开发者自动管理内存,避免内存泄漏和悬空指针等问题。智能指针通过RAII(资源获取即初始化)理念确保资源的自动释放。

 std::shared_ptr

初始化

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,shared_ptr 可以被复制,每次复制时引用计数增加,当最后一个指针被销毁时,资源才会被释放。共享智能指针share_ptr是一个模板类,如果进行初始化有三种方式如下:

1.通过构造函数初始化

2.std::make_shared辅助函数

3.reset方法

共享智能指针对象初始化完毕之后就指向了要管理的那块堆区内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count。

构造函数初始化
#include<iostream>
#include<memory>
using namespace std;
/*
share_ptr:共享智能指针,一块堆区内存可以被多个share_ptr所指向
*/
int main()
{
    //初始化 share_ptr
    //构造函数初始化
    shared_ptr<int>ptr1(new int(520));
    cout<<"ptr1的引用计数:"<<ptr1.use_count()<<endl;
    shared_ptr<char>ptr2(new char[520]);//指向一块字符数组对应的堆区内存
    cout<<"ptr2的引用计数:"<<ptr2.use_count()<<endl;
    shared_ptr<int>ptr3;//默认构造
    cout<<"ptr3的引用计数:"<<ptr3.use_count()<<endl;
    shared_ptr<int>ptr4(nullptr);
    cout<<"ptr4的引用计数:"<<ptr4.use_count()<<endl;
}

结果:
    ptr1的引用计数:1
    ptr2的引用计数:1
    ptr3的引用计数:0
    ptr4的引用计数:0
// 如果智能指针被初始化了一块有效内存,那么引用计数+1,如果智能指针没有被初始化
// 或者初始化为空,引用计数为0;
 std::make_shared()

std::make_shared()就可以完成内存对象的创建并将其初始化给智能指针。

#include<iostream>
#include<string>
#include<memory>
using namespace std;
class Test
{
  public:
  Test()
  {
      cout<<"无参构造函数"<<endl;  
  }  
  Test(int x)
  {
      cout<<"int类型构造函数"<<x<<endl;  
  }
  Test(string str)
  {
      cout<<"string类型的构造函数"<<str<<endl;  
  }
  ~Test()
  {
      cout<<"析构函数"<<endl;  
  }
};
int main()
{
    //使用智能指针管理一块int型的堆区内存,内部引用计数为1
    shared_ptr<int>ptr1=make_shared<int>(520);
    cout<<"ptr1管理的内存引用计数:"<<ptr1.use_count()<<endl;
    
    shared_ptr<Test>ptr2=make_shared<Test>();
    cout<<"ptr2管理的内存引用计数:"<<ptr2.use_count()<<endl;
    
    shared_ptr<Test>ptr3=make_shared<Test>();
    cout<<"ptr3管理的内存引用计数:"<<ptr3.use_count()<<endl;
    
    shared_ptr<Test>ptr4=make_shared<Test>("AAAA");
    cout<<"ptr4管理的内存引用计数:"<<ptr4.use_count()<<endl;
    
    shared_ptr<Test>ptr5(new Test(200));
    cout<<"ptr5管理的内存引用计数:"<<ptr5.use_count()<<endl;
    
    打印如下
    ptr1管理的内存引用计数:1
    无参构造函数
    ptr2管理的内存引用计数:1
    int类型构造函数 520
    ptr3管理的内存引用计数:1
    string类型的构造函数AAAA
    ptr4管理的内存引用计数:1
    int类型的构造函数
    ptr5管理的内存引用计数:1
    
    析构函数
    析构函数
    析构函数
    析构函数
}
 reset方法
#include<iostream>
#include<string>
#include<memory>
using namespace std;
int main()
{
    //使用智能指针管理一块int型的堆区内存,内部引用计数为1
    shared_ptr<int>ptr1=make_shared<int>(520);
    shared_ptr<int>ptr2=ptr1
    shared_ptr<int>ptr3=ptr1
    shared_ptr<int>ptr4=ptr1
    
     
    cout<<"ptr1管理的内存引用计数:"<<ptr1.use_count()<<endl;
    cout<<"ptr2管理的内存引用计数:"<<ptr2.use_count()<<endl;
    cout<<"ptr3管理的内存引用计数:"<<ptr3.use_count()<<endl;
    cout<<"ptr4管理的内存引用计数:"<<ptr4.use_count()<<endl;
    
    ptr4.reset();
    cout<<"ptr1管理的内存引用计数:"<<ptr1.use_count()<<endl;
    cout<<"ptr2管理的内存引用计数:"<<ptr2.use_count()<<endl;
    cout<<"ptr3管理的内存引用计数:"<<ptr3.use_count()<<endl;
    cout<<"ptr4管理的内存引用计数:"<<ptr4.use_count()<<endl;
    shared_ptr<int>ptr5;
    ptr5.reset(new int(250));
     cout<<"ptr5管理的内存引用计数:"<<ptr5.use_count()<<endl;
     return 0;
}

打印结果:
     ptr1管理的内存引用计数:4
     ptr2管理的内存引用计数:4
     ptr3管理的内存引用计数:4
     ptr4管理的内存引用计数:4
     
     ptr1管理的内存引用计数:3
     ptr2管理的内存引用计数:3
     ptr3管理的内存引用计数:3
     ptr4管理的内存引用计数:0
     
     ptr5管理的内存引用计数:1

注意 :不要使用同一个原始指针初始化不同的智能指针,会造成同一块内存重复释放。

例:

 int*p =new int(12);
 shared_ptr<int>p1(p),p2(p);
 cout<<p1.use_count()<<endl;   
 cout<<p2.use_count()<<endl;   
 //会报错

拷贝构造和移动构造函数初始化

当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被调用了。

//构造函数
shared_ptr<int>ptr1(new int(520));
cout<<"ptr1管理的内存引用计数:"<<ptr1.use_count()<<endl;
//拷贝构造函数
shared_ptr<int>ptr2(ptr1);
cout<<"ptr2管理的内存引用计数:"<<ptr2.use_count()<<endl;
shared_ptr<int>ptr3=ptr1;
cout<<"ptr3管理的内存引用计数:"<<ptr3.use_count()<<endl;
//移动构造函数
shared_ptr<int>ptr4(std::move(ptr1));
cout<<"ptr4管理的内存引用计数:"<<ptr4.use_count()<<endl;
std::shared_ptr<int> ptr5=std::move(ptr2);
acout<<"ptr5管理的内存引用计数:"<<ptr5.use_count()<<endl;
打印结果如下:
        ptr1管理的内存引用计数:1
        ptr2管理的内存引用计数:2
        ptr3管理的内存引用计数:3
        ptr4管理的内存引用计数:3
        ptr5管理的内存引用计数:3

如果使用拷贝的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用技术也会增加。如果使用移动构造函数的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用技术不会增加。

获取原始指针

get()函数

int main()
{shared_ptr<int> p(new int)
*p=100;
cout<<*p.get()<<" "<<*p<<endl;
return 0;
}

结果:
    100 100

std::unique_ptr

初始化

1.独占所有权:std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针。

2.不可复制:可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。

3.自动释放:当 unique_ptr 超出作用域时,自动释放内存。

#include<iostream>
#include<string>
#include<memory>
using namespace std;
int main()
{
	//通过构造函数初始化对象
	unique_ptr<int> ptr1(new int(10));
    std::cout << *ptr1 << std::endl; // 输出 10  

    unique_ptr<int> p2 = std::move(p1); // 移动所有权 
	
    //下面写法错误不可复制
	unique_ptr<int>ptr2 = ptr1;//报错
	return 0;
}

 

unique_ptr不允许被复制,但是可以通过函数返回给其他的unique_ptr,还可以通过std::move()转移给其他的unique_ptr。还是一个unique_ptr独占一个地址。

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

unique_ptr<int> fun()
{
	//独占智能指针unique_ptr 它不允许其他的智能指针共享内部的指针
	unique_ptr<int> a(new int(10));
	return a;
}


int main()
{
	unique_ptr<int> b = fun();//移动构造
	cout << *b << endl;//10
	return 0;
}

 

使用reset方法可以让unique_ptr解除对原始内存的管理。也可以用来初始化一个独占的智能指针。

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


int main()
{
	unique_ptr<int> ptr1(new int(10));
	unique_ptr<int>ptr2;
	ptr1.reset();//解除对原始内存的管理
	ptr2.reset(new int(250));//重新指定智能指针管理的原始内存

	
	return 0;
}

 

 如果想要获取独占智能指针管理的原始地址,可以调用get()方法。

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


int main()
{
	unique_ptr<int> ptr1(new int(10));
	unique_ptr<int>ptr2=move(ptr1);
	ptr2.reset(new int(250));
	cout << *ptr2.get() << endl;//得到内存地址中储存的实际数值

	return 0;
}


结果:
      250

std::weak_ptr

弱引用智能指针std::weak_ptr可以看作是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。

防止循环引用:通常与 shared_ptr 一起使用,可以避免因循环引用导致的内存泄漏。

 初始化

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


int main()
{
	shared_ptr<int> sp(new int);

	//week_ptr<int>wp1; 构造了一个空weak_ptr对象weak_ptr<int>wp1;
	weak_ptr<int>wp1;

	//week_ptr<int>wp2(wp1);通过构造了一个空weak_ptr对象构造了另一个空weak_ptr对象
	weak_ptr<int>wp2(wp1);

	//week_ptr<int>wp3(sp);通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
	 weak_ptr<int>wp3(sp);

    //wp4=sp;通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象(这是一个隐式类型转换)
	  weak_ptr<int>wp4;
	   wp4 = sp;

	//wp5 = wp3; 通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象
	   weak_ptr<int>wp5;
	   wp5 = wp3;

		 return 0;
}

循环引用

例:

#include <iostream>  
#include <memory>  

class B;  // 前向声明  

class A {  
public:  
    std::shared_ptr<B> b_ptr;  
    ~A() { std::cout << "A destructed\n"; }  
};  

class B {  
public:  
    std::shared_ptr<A> a_ptr;  
    ~B() { std::cout << "B destructed\n"; }  
};  

void example() {  
    std::shared_ptr<A> a(new A());  
    std::shared_ptr<B> b(new B());  

    a->b_ptr = b;  // A 持有 B 的共享指针  
    b->a_ptr = a;  // B 持有 A 的共享指针  
}  

int main() {  
    example();  
    // 此时不会调用 A 和 B 的析构函数,因为它们相互持有,引用计数不为零  
    return 0;  
}

 当 example 函数返回时,a 和 b 的引用计数都不会降为零,因此它们的析构函数不会被调用,导致内存泄漏。

解决循环引用

为了解决循环引用的问题,通常可以使用 std::weak_ptrstd::weak_ptr 是一种对 std::shared_ptr 的非拥有权引用,不会增加引用计数,因此可以用来打破循环引用。

#include <iostream>  
#include <memory>  

class B;  // 前向声明  

class A {  
public:  
    std::weak_ptr<B> b_ptr;  // 使用 weak_ptr  
    ~A() { std::cout << "A destructed\n"; }  
};  

class B {  
public:  
    std::shared_ptr<A> a_ptr;  
    ~B() { std::cout << "B destructed\n"; }  
};  

void example() {  
    std::shared_ptr<A> a(new A());  
    std::shared_ptr<B> b(new B());  

    a->b_ptr = b;  // A 持有 B 的弱引用  
    b->a_ptr = a;  // B 持有 A 的共享指针  
}  

int main() {  
    example();  
    // 此时 A 和 B 的析构函数都会被调用,因为弱引用不影响引用计数  
    return 0;  
}

use_count()

通过调用tsd::weak_ptr类提供的use_count()方法可以获得当前所观察资源的引用计数。

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


int main()
{
	shared_ptr<int> sp(new int);
	weak_ptr<int>wp1;
	weak_ptr<int>wp2(wp1);
	 weak_ptr<int>wp3(sp);
	  weak_ptr<int>wp4;
		 wp4 = sp;
		 weak_ptr<int>wp5;
		 wp5 = wp3;
		 cout << "use_count:" <<  endl;
		 cout << "wp1:" << wp1.use_count() << endl;
		 cout << "wp2:" << wp2.use_count() << endl;
		 cout << "wp3:" << wp3.use_count() << endl;
		 cout << "wp4:" << wp4.use_count() << endl;
		 cout << "wp5:" << wp5.use_count() << endl;
		 return 0;
}

结果:
    use_count:
    wp1:0
    wp2:0
    wp3:1
    wp4:1
    wp5:1

 通过打印的结果知道,虽然弱引用智能指针 wp3,wp4,wp5检测的资源是同一个但是引用计数并没有发生任何变化,也进一步证明了weak_ptr只是检测资源,并不管理资源。

expired()

通过调用std::weah_ptr类提供的expired()方法来观测的资源是否已经释放。

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

int main()
{
	shared_ptr<int> shared(new int(10));
	weak_ptr<int>weak(shared);
	cout << "1  weak" << (weak.expired() ? "is" : "is not") << "expired" << endl;

	shared.reset();
	cout << "2 weak" << (weak.expired() ? "is" : "is not") << "expired" << endl;
		
		 return 0;
}

结果:
    1  weak is not xpired
    2 weak is expired

 weak_ptr检测的就是shared_ptr管理的资源,当共享智能指针调用shared.reset()之后管理的资源被释放,因此weak.expired()函数的结果返回true,表示监视的资源已经不存在了。

lock

通过调用std::weak_ptr类提供的lock()方法来获取管理检测的shared_ptr对象。

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


int main()
{
    shared_ptr<int> sp1, sp2;
    weak_ptr<int>wp;

    sp1 = std::make_shared<int>(520);
    wp = sp1;
    sp2 = wp.lock();
    cout << "use_count:" << wp.use_count() << endl;

    sp1.reset();
    cout << "use_count:" << wp.use_count() << endl;

    sp1 = wp.lock();
    cout << "use_count:" << wp.use_count() << endl;
    
    
    cout << "*sp1:" << wp.use_count() << endl;
    cout << "*sp2:" << wp.use_count() << endl;
    return 0;
}

结果:
    use_count:2
    use_count:1
    use_count:2
    *sp1:2
    *sp2:2

sp2=wp.lock();通过调用lock()方法得到用于管理wseak_ptr对象所检测的资源的共享智能指针对象,使用这个对象初始化sp2,此时所检测资源的引用计数为2。

sp1.reset()。共享智能指针sp1被重置weak_ptr对象所检测的资源的引用计数减1。

sp1=wp.lock().sp1重新被初始化,并且管理的还是weak_ptr对象所检测的资源,因此引用计数加1。

共享智能指针对象sp1和sp2管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是520。

reset

通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不检测任何资源。

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

int main()
{
	shared_ptr<int> sp(new int(10));
	weak_ptr<int>wp(sp);
	cout << "1  wp" << (wp.expired() ? "is" : "is not") << "expired" << endl;

	wp.reset();
	cout << "2 wp" << (wp.expired() ? "is" : "is not") << "expired" << endl;
		
		 return 0;
}

结果:
    wp is not expired
    wp is expired

上一篇:C++ 链表的实现


下一篇:C语言 | Leetcode C语言题解之第543题二叉树的直径