【基础知识】智能指针shared_ptr、weak_ptr、unique_ptr

        C++ 不像 Java 自带垃圾回收机制,必须释放掉分配的内存,否则就会造成内存泄漏。因此 C++11 引入了智能指针。

定义:

        智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在区域时,自动地销毁分配的对象,防止内存泄漏

        智能指针的核心实现技术是引用技术,每使用它一次,内部引用计数加 1,每析构一次内部引用计数减 1,减为 0 时,删除原始指针指向的堆区内存,使用智能指针需要引用头文件

  • std::shared_ptr 共享的智能指针
  • std::weak_ptr 弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的,解决循环引用问题
  • std::unique_ptr 独占的智能指针

一、shared_ptr(共享智能指针)

1.初始化

        共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 share_ptr 是一个模板类,如果进行初始化,有三种方式:

  • 通过构造函数初始化
  • std::make_shared 辅助函数
  • reset 方法

(1)use_count 成员函数

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

(2)构造函数初始化

    //使用智能指针管理一块 int 型的堆内存
	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,如果智能指针没有被初始化或者被初始化为 nullptr 空指针,引用计数为 0。另外,不要使用一个原始指针初始化多个 shared_ptr 。

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

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

    //构造函数
	cout << "构造函数" << endl;
	shared_ptr<int> ptr1(new int(520));
	cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl;

	//拷贝构造函数
	cout << "拷贝构造函数" << endl;
	//ptr2拷贝构造ptr1,先让ptr1的引用计数+1,再让ptr2的引用计数等于ptr2的引用计数
	shared_ptr<int> ptr2(ptr1);
	cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;
	shared_ptr<int> ptr3 = ptr1;
	cout << "ptr3管理的内存引用计数:" << ptr3.use_count() << endl;

	//移动构造函数
	cout << "移动构造函数" << endl;
	//把ptr1的指针给ptr4,再把ptr1的引用计数给ptr4,然后把ptr1的指针和引用计数置空
	shared_ptr<int> ptr4(std::move(ptr1));
	cout << "ptr4管理的内存引用计数:" << ptr4.use_count() << endl;
	cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl;
	shared_ptr<int> ptr5 = move(ptr2);
	cout << "ptr5管理的内存引用计数:" << ptr5.use_count() << endl;
	cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;


//输出结构
*/
构造函数
ptr1管理的内存引用计数:1
拷贝构造函数
ptr2管理的内存引用计数:2
ptr3管理的内存引用计数:3
移动构造函数
ptr4管理的内存引用计数:3
ptr1管理的内存引用计数:0
ptr5管理的内存引用计数:3
ptr2管理的内存引用计数:0
*/

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

(4)std::make_shared 初始化

        通过 C++11 提供的 std::make_shared() 就可以完成内存对象的创建,并将其初始化给智能指针。

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


class Test
{
public:
	Test()
	{
		cout << "无参构造函数" << endl;
	}
	Test(int x)
	{
		cout << "int类型构造函数" << endl;
	}
	Test(string str)
	{
		cout << "string类型的构造函数" << endl;
	}
	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	//使用智能指针管理一块 int 型的堆内存,内部引用计数为1
	shared_ptr<int> ptr1 = make_shared<int>(520);
	cout << "ptr2管理的内存引用计数:" << ptr1.use_count() << endl;

	//调用无参构造函数
	shared_ptr<Test> ptr2 = make_shared<Test>();
	cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;

	//调用int类型构造函数
	shared_ptr<Test> ptr3 = make_shared<Test>(520);
	cout << "ptr3管理的内存引用计数:" << ptr3.use_count() << endl;

	//调用string类型构造函数
	shared_ptr<Test> ptr4 = make_shared<Test>("QQQQQ");
	cout << "ptr4管理的内存引用计数:" << ptr4.use_count() << endl;

	return 0;
}


//输出
/*
    ptr2管理的内存引用计数:1
    无参构造函数
    ptr2管理的内存引用计数:1
    int类型构造函数
    ptr3管理的内存引用计数:1
    string类型的构造函数
    ptr4管理的内存引用计数:1
    析构函数
    析构函数
    析构函数
*/

(5)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
*/

        对于一个未初始化的共享智能指针,可以通过 reset 方法来初始化,当智能指针中有值的时候,调用 reset 会使引用计数减一。

2.获取原始指针

        get() 函数

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


//100  100

二、weak_ptr(弱引用智能指针)

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

1.初始化

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

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

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

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

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

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

	return 0;
}

2.use_count()

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

#include <iostream>
#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 只是监测资源,并不管理资源。

3.expired()

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

#include <iostream>
#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 expired
2. weak is expired
*/

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

4.lock()

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

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


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

	sp1 = make_shared<int>(520);//用make_shared初始化shared_ptr
	wp = sp1;//wp监测sp1里面的资源(指针、引用计数)
	sp2 = wp.lock();//通过调用lock方法得到一个用于管理weak_ptr对象所监测的资源的共享智能对象指针,使用这个对象初始化sp2,此时所监测资源的引用计数为2
	cout << "use_count:" << wp.use_count() << endl;

	sp1.reset();//共享智能指针sp1被重置,weak_ptr对象所监测的资源的引用计数减一
	cout << "use_count:" << wp.use_count() << endl;

	sp1 = wp.lock();//sp1被重新初始化,并且管理的还是weak_ptr对象所监测的资源,引用计数加1
	cout << "use_count:" << wp.use_count() << endl;

	//共享智能指针对象sp1和sp2管理的是同一块内存,最终打印的结果是相同的,都是520
	cout << "*sp1 = " << *sp1 << endl;
	cout << "*sp2 = " << *sp2 << endl;

	return 0;

}


//输出
/*
    use_count:2
    use_count:1
    use_count:2
    *sp1 = 520
    *sp2 = 520
*/

5.reset()

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

#include <iostream>
#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;
}
//输出
/*
1. wp is not expired
2. wp is expired
*/

6.管理返回this的shared_ptr

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

struct Test
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_ptr<Test>(this);
	}

	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	shared_ptr<Test> sp1(new Test);
	cout << "引用计数:" << sp1.use_count() << endl;
	
	shared_ptr<Test> sp2 = sp1->getSharedPtr();
	cout << "引用计数:" << sp1.use_count() << endl;

	return 0;

}

//输出
/*
引用计数:1
引用计数:1
析构函数
析构函数
*/

        通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针 this 构造了两个智能指针对象 sp1 和sp2,这两者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象。在离开作用域之后,this 将被构造的两个智能指针各自析构,导致重复析构的错误。

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

struct Test : public enable_shared_from_this<Test>
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_from_this();
	}
	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	shared_ptr<Test> sp1(new Test);
	cout << "引用个数: " << sp1.use_count() << endl;
	shared_ptr<Test> sp2 = sp1;
	cout << "引用个数: " << sp1.use_count() << endl;
	return 0;
}

//输出
/*
引用个数: 1
引用个数: 2
析构函数
*/

7.循环引用问题

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

class A;
class B;

class A
{
public:
	shared_ptr<B> bptr;
	~A()
	{
		cout << "class TA is disstruct ..." << endl;
	}
};

class B
{
public:
	shared_ptr<A> aptr;
	~B()
	{
		cout << "class TB is disstruct ..." << endl;
	}
};

void testPtr()
{
	shared_ptr<A> ap(new A);
	shared_ptr<B> bp(new B);
	cout << "A的引用计数:" << ap.use_count() << endl;
	cout << "B的引用计数:" << bp.use_count() << endl;

	ap->bptr = bp;
	bp->aptr = ap;
	cout << "A的引用计数:" << ap.use_count() << endl;
	cout << "B的引用计数:" << bp.use_count() << endl;
}


int main()
{
	testPtr();
	return 0;

}

//输出
/*
A的引用计数:1
B的引用计数:1
A的引用计数:2
B的引用计数:2
*/

        在这段代码中,我们定义了两个类 A 和 B,它们之间相互包含对方的 shared_ptr。这样的情况下,当这两个 shared_ptr 交叉引用时,会导致引用计数不会降为零,从而导致内存泄漏。

让我们来分析一下 testPtr 函数的执行过程:

  • 在 testPtr 函数中,首先创建了一个名为 ap 的 shared_ptr 智能指针,指向一个 A 类对象,并输出 ap 的引用计数(初始引用计数应该为1)。
  • 然后,创建了一个名为 bp 的 shared_ptr 智能指针,指向一个 B 类对象,并输出 bp 的引用计数(初始引用计数应该为1)。
  • 接着,ap->bptr = bp; 将 bp 赋值给 ap 中的 bptr,并且 bp->aptr = ap; 将 ap 赋值给 bp 中的 aptr,这样两个智能指针就相互交叉引用了,形成了循环引用。
  • 最后,输出 ap 和 bp 的引用计数,你会发现它们的引用计数仍然是1,没有减少为0,因为它们之间的循环引用导致了内存泄漏。

        这段代码中的循环引用会导致 A 和 B 对象永远无法被正确地释放,因为它们的引用计数永远不会降为零。这就是为什么在 C++ 中要避免循环引用的原因。

        为了解决这个问题,你可以使用 weak_ptr 来打破 shared_ptr 的循环引用。weak_ptr 允许共享资源但不增加引用计数,因此当 shared_ptr 的引用计数归零时,weak_ptr 就不会阻止资源的释放。

        共享智能指针 ap、bp 对 A、B 实例对象的引用计数变为 2,在共享智能指针离开作用域之后,引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 A、B 的实例对象不能被析构,最终造成内存泄漏。通过使用 weak_ptr 可以解决这个问题,只需要将类 A 或者 B 的任意一个成员改为 weak_ptr。

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

class A;
class B;

class A
{
public:
	weak_ptr<B> bptr;
	~A()
	{
		cout << "A 的 析构函数" << endl;
	}
};

class B
{
public:
	shared_ptr<A> aptr;
	~B()
	{
		cout << "B 的 析构函数" << endl;
	}
};

void testPtr()
{
	shared_ptr<A> ap(new A);
	shared_ptr<B> bp(new B);
	cout << "A 的 引用计数: " << ap.use_count() << endl;
	cout << "B 的 引用计数: " << bp.use_count() << endl;

	ap->bptr = bp;
	bp->aptr = ap;
	cout << "A  的 引用计数: " << ap.use_count() << endl;
	cout << "B  的 引用计数: " << bp.use_count() << endl;
}

int main()
{
	testPtr();
	return 0;
}

//输出
/*
A 的 引用计数: 1
B 的 引用计数: 1
A  的 引用计数: 2
B  的 引用计数: 1
B 的 析构函数
A 的 析构函数
*/

        在上面程序中,在对类 A 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域后,bp 的引用计数减为 0,类 B 的实例对象被析构。

        在类 B 的实例对象被析构的时候,内部的 aptr 也被析构,其对 A 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 A 对象的管理也解除了,内存的引用计数减为 0,类 A 的实例对象被析构。

三、unique_ptr(独占智能指针)

1.初始化

        std::unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个 unique_ptr 赋值给另一个 unique_ptr

#include 
#include 
#include 
using namespace std;

int main()
{
	// 通过构造函数初始化对象
	unique_ptr<int> ptr1(new int(10));
	//  报错
	unique_ptr<int> ptr2 = ptr1;
	return 0;
}

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

2.reset()

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

#include <iostream>
#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;
}

3.get()

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

#include <iostream>
#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;	// 得到内存地址中存储的实际数值 250
	return 0;
}

//输出
    250

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值