菜鸟学习使用三种C++智能指针

智能指针是一个类对象。在对象生命周期结束,将会被delete(栈区对象)。不仅对象会被删除,它指向的内存也会被自动delete,以免造成内存泄漏。

首先来看一个错误实例:

int main()
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	vector<Student*> arr;
	for (int i = 0; i < 10; i++)
	{
		Student *p = new Student(i);
		arr.push_back(p);
	}

	cin.get();
	return 0;
}

菜鸟学习使用三种C++智能指针_第1张图片
再来看一个解决方案:

class AutoDestoryStudent
{
public:
	AutoDestoryStudent()
	{
		m_p = NULL;
	}
	~AutoDestoryStudent()
	{
		if (m_p)
		{
			delete m_p;
			m_p = NULL;
		}
	}
	Student* CreateObj(int nID)
	{
		return m_p = new Student(nID);
	}
private:
	Student* m_p;
};
int main()
{
	#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	vector<Student*> arr;
	for (int i = 0; i < 10; i++)
	{
		AutoDestoryStudent obj;
		arr.push_back(obj.CreateObj(i));
	}
	cin.get();
	return 0;
}

菜鸟学习使用三种C++智能指针_第2张图片

1、shared_ptr。
shared_ptr可以让多个对象托管同一个指针,n个shared_ptr对象托管同一指针p。当指针p的Count为0时,该指针会被delete。shared_ptr对象消亡或托管了新的指针,都会导致原托管指针的Count–。

Tip1: shared_ptr所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个“人”同时持有,就像原始指针一样。

Tip2: shared_ptr 具有完整的“值语义”(即可以拷贝赋值),所以,它可以在任何场合替代原始指针,而不用再担心资源回收的问题,比如用于容器存储指针、用于函数安全返回动态创建的对象

包含头文件:

#include 
#include 
#include 
#include 

using namespace std;

构建类Student:

class Student
{
public:
	Student(int n) :m_nID(n)
	{
	};
	~Student()
	{
		cout << m_nID << "\t~析构~\n";
	}
	int GetID()
	{
		return m_nID;
	}
private:
	int m_nID;
};
int main()
{

//1.0 多个shared_ptr可以共同管理同一个对象。
	shared_ptr<Student> sp1 = make_shared<Student>(2);//Student(2)由sp1托管,
	shared_ptr<Student> sp2 = sp1;					  //Student(2)同时交由sp2托管
	shared_ptr<Student> sp3 = sp2;					 //Student(2)同时交由sp3托管

	cout << sp1->GetID() << "\t" 
		<< sp2->GetID() << "\t" 
		<< sp3->GetID() << "\n\n";

//1.1 get()方法可以返回托管的指针。
	Student * p = sp3.get();				// get返回托管的指针,p 指向 Student(2)
	cout << p->GetID() << endl;				//输出 2

//1.2 reset()方法托管新的指针。
	sp1.reset(new Student(3));					// reset导致托管新的指针, 此时sp1托管Student(3)
	cout << sp1->GetID() << endl;			//输出 3
	sp2.reset(new Student(4));				// sp2托管Student(4)

	sp3.reset(new Student(5));				// sp3托管Student(5),Student(2)无人托管,被delete
	cout << "end" << endl;

	cin.get();
	return 0;
}

菜鸟学习使用三种C++智能指针_第3张图片
现在用share_ptr解决开头的问题:

	const int nSize = 10;
	vector<shared_ptr<Student>> arr;
	for (int i = 0; i < nSize; i++)
	{
		shared_ptr<Student> sp = make_shared<Student>(i);
		arr.push_back(sp);
	}

Tip3: shared_ptr 的引用计数也导致了一个新的问题,就是“循环引用”,这在把 shared_ptr 作为类成员的时候最容易出现,典型的例子就是链表节点。

分析:两个节点指针刚创建时,引用计数都是 1。但指针互指(即拷贝赋值)之后,引用计数都变成了 2。shared_ptr 就无法意识不到这是一个循环引用,多算了一次计数,后果就是引用计数无法减到 0,无法调用析构函数执行 delete,最终导致内存泄漏。

想要从根本上杜绝循环引用,必须要用到它的“小帮手”:weak_ptr。

2、weak_ptr。

//接着上文的例子,来看接下来的程序:


class Node final
{
public:
    using this_type     = Node;

    // 注意这里,别名改用weak_ptr
    using shared_type   = std::weak_ptr<this_type>;
public:
    shared_type     next;    // 因为用了别名,所以代码不需要改动
};

auto n1 = make_shared<Node>();  // 工厂函数创建智能指针
auto n2 = make_shared<Node>();  // 工厂函数创建智能指针

n1->next = n2;             // 两个节点互指,形成了循环引用
n2->next = n1;

assert(n1.use_count() == 1);    // 因为使用了weak_ptr,引用计数为1
assert(n2.use_count() == 1);   // 打破循环引用,不会导致内存泄漏

if (!n1->next.expired()) {     // 检查指针是否有效
    auto ptr = n1->next.lock();  // lock()获取shared_ptr
    assert(ptr == n2);
}

详细来讲, weak_ptr 是为配合协助shared_ptr工作而引入的一种智能指针,用来解决shared_ptr相互引用时的死锁问题。它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用Count的增加或减少。如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用Count。
C++允许weak_ptr和shared_ptr相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr可通过调用lock()获得shared_ptr。


int main()
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	shared_ptr<Student> ptrShare6(new Student(666666));
	shared_ptr<Student> ptrShare7 = ptrShare6;
	weak_ptr<Student> ptrWeak = ptrShare6;

	cout << ptrWeak.use_count() << endl;//2
	shared_ptr<Student> ptrShare8 = ptrWeak.lock();//转化
	cout << ptrShare7.use_count() << endl;
	cout << ptrShare8.use_count() << endl;//3

	ptrShare8.reset();
	cout << ptrShare6.use_count() << endl;

	ptrShare7.reset();
	cout << ptrShare6.use_count() << endl;

	ptrShare6.reset();
	cout << ptrShare6.use_count() << endl;

	cin.get();
	return 0;
}

菜鸟学习使用三种C++智能指针_第4张图片
菜鸟学习使用三种C++智能指针_第5张图片

3、unique_ptr。
unique_ptr,唯一的,独一无二,只允许自己访问相关内存。

创建智能指针对象方法:使用工厂函数创建智能指针。

int main()
{
	auto pS1= make_unique<int>(123);               // 工厂函数创建智能指针
	assert(pS1&& *pS1== 123);
	cout << *pS1<< endl;

	auto pS2= make_unique<string>("Test string");  // 工厂函数创建智能指针
	assert(!pS2->empty());
	cout << *pS2<< endl;;

	cin.get();
	return 0;
}

//自定义的工厂函数make_unique()

template<class T, class... Args>              // 可变参数模板
std::unique_ptr<T>                            // 返回智能指针
my_make_unique(Args&&... args)                // 可变参数模板的入口参数
{
	return std::unique_ptr<T>(                // 构造智能指针
		new T(std::forward<Args>(args)...));    // 完美转发
}

int main()
{
	auto pS3 = my_make_unique<string>("my_make_unique");  // 工厂函数创建智能指针
	assert(!pS3->empty());
	cout << *pS3 << endl;;

	cin.get();
	return 0;
}


int main()
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	/*
	unique_ptr唯一的,独一无二.只允许自己访问相关内存
	*/
	//3.1  重置(reset)unique_ptr
	unique_ptr<Student> ptrUnique(new Student(111111));
	cout << "unique_ptr:\t" << ptrUnique->GetID() << endl;
	//调用 unique_ptr 的 reset() 方法将删除与之绑定的 raw 指针,并且将 unique_ptr 对象置空:
	ptrUnique.reset();//相当于析构。(delete ptrUnique; ptrUnique = nullptr;)
	//3.2错误示例  unique_ptr 独一无二,不可拷贝,只能移动。我们不能通过拷贝构造函数或赋值操作拷贝unique_ptr对象:
	//std::unique_ptr ptrUnique2 = ptrUnique; //error

	//3.3  unique_ptr 对象的所有权.
	/*
	unique_ptr对象不能拷贝,但可以移动。移动,也就是转移所有权。
	*/
	unique_ptr<Student> ptrUnique2(new Student(222222));
	unique_ptr<Student> ptrUnique3 = std::move(ptrUnique2);
	if (ptrUnique2 == nullptr)
	{
		cout << "ptrUnique2 is empty.\n" << std::endl;//转移
	}
	if (ptrUnique3 != nullptr)
	{
		cout << "ptrUnique3 is not empty.\n" << std::endl;
	}

	//3.4  unique_ptr的release()函数可直接将绑定raw指针的所有权释放,该函数会将绑定的 raw 指针返回,
	unique_ptr<Student> ptrUnique4(new Student(444444));
	if (ptrUnique4 != nullptr)
	{
		cout << "None empty.\n" << endl;
	}

	Student *p5 = ptrUnique4.release();//unique_ptr4释放raw所有权,并且移交给p5
	if (ptrUnique4 == nullptr)
	{
		cout << "Empty.\n" << endl;
	}
	if (p5)
	{
		cout << "P5 get.\n" << endl;
		delete p5;
		p5 = nullptr;
	}

	cin.get();
	return 0;
}

菜鸟学习使用三种C++智能指针_第6张图片
小结:
1.如果指针是“独占”使用,就应该选择 unique_ptr,它为裸指针添加了很多限制,更加安全。
2.如果指针是“共享”使用,就应该选择 shared_ptr,它的功能非常完善,用法几乎与原始指针一样。
3.应当使用工厂函数 make_unique()、make_shared() 来创建智能指针,强制初始化,而且还能使用 auto 来简化声明。
4.shared_ptr 有少量的管理成本,也会引发一些难以排查的错误,所以不要过度使用。

参考并且推荐罗剑锋老师的C++实战笔记,干货,满满的干货!

你可能感兴趣的:(C/C++知识点)