6、C++指针(三):智能指针与内存泄漏

目录

  • 一、内存泄漏
  • 二、智能指针概述
  • 三、auto_ptr
  • 四、unique_ptr
  • 五、shared_ptr
  • 六、weak_ptr
  • 七、引用
    • 关于函数传递参数类型

一、内存泄漏

  • 什么是内存泄漏:指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
  • 内存泄漏发生原因:内存泄漏主要发生在堆内存分配方式中,即“配置了内存后,所有指向该内存的指针都遗失了”;若缺乏语言这样的垃圾回收机制,这样的内存片就无法归还系统
  • 内存泄漏排查方式:因为内存泄漏属于程序运行中的问题,无法通过编译识别,所以只能在程序运行过程中来判别和诊断
  • 运行如下代码:在任务管理器查看如下代码的程序,可以发现,内存一直在增加不会减少
#include 


int main()
{
	while (true)
	{
		int* wp = new int(10);
	}
	return 0;
}

二、智能指针概述

  • 使用指针存在的问题:使用指针是非常危险的行为,可能存在空指针、野指针问题,并可能造成内存泄漏问题;可指针又非常的高效,所以我们希望以更安全的方式来使用指针
  • 安全使用指针的解决方案
    • 使用更安全的指针 —— 智能指针
    • 不使用指针,使用更安全的方式 —— 引用
  • C++中四种常用的智能指针:unique_ptr、shared_ptr、weak_ptr和auto_ptr(C++11中已经废弃的deprecated,在C++17中被正式删除)

三、auto_ptr

  • auto_ptr使用:由new expression或得对象,在auto_ptr对象销毁时,他所管理的对象也会自动被delete掉
  • 所有权转移
    • 不小心把它传递给另外的智能指针,原来的指针就不再拥有这个对象了
    • 在拷贝/赋值过程中,会直接剥夺指针对原对象对内存的控制权,转交给新对象,然后再将原对象指针置为nullptr
#include 

using namespace std;
int main()
{
	{// 确定auto_ptr失效的范围
		// 对int使用
		auto_ptr<int> pI(new int(10));
		cout << *pI << endl;                // 10 

		// C++ 11之后判断空指针使用nullptr,不再使用NULL

		// auto_ptr	C++ 17中移除	拥有严格对象所有权语义的智能指针
		// auto_ptr原理:在拷贝 / 赋值过程中,直接剥夺原对象对内存的控制权,转交给新对象,
		// 然后再将原对象指针置为nullptr(早期:NULL)。这种做法也叫管理权转移。
		// 他的缺点不言而喻,当我们再次去访问原对象时,程序就会报错,所以auto_ptr可以说实现的不好,
		// 很多企业在其库内也是要求不准使用auto_ptr。
		auto_ptr<string> languages[5] = {
			auto_ptr<string>(new string("C")),
			auto_ptr<string>(new string("Java")),
			auto_ptr<string>(new string("C++")),
			auto_ptr<string>(new string("Python")),
			auto_ptr<string>(new string("Rust"))
		};
		cout << "There are some computer languages here first time: \n";
		for (int i = 0; i < 5; ++i)
		{
			cout << *languages[i] << endl;
		}
		auto_ptr<string> pC;
		pC = languages[2]; // languges[2] loses ownership. 将所有权从languges[2]转让给pC,
		//此时languges[2]不再引用该字符串从而变成空指针
		cout << "There are some computer languages here second time: \n";
		for (int i = 0; i < 5; ++i)
		{
			cout << *languages[i] << endl;
		}
		cout << "The winner is " << *pC << endl;
	}
	return 0;
}

6、C++指针(三):智能指针与内存泄漏_第1张图片

四、unique_ptr

  • unique_ptr概念:unique_ptr是专属所有权,所以unique_ptr管理的内存,只能被一个对象持有,不支持复制和赋值
  • 移动语义:unique_ptr禁止了拷贝语义,但有时我们也需要能够转移所有权,于是提供了移动语义,即可以使用std::move()进行控制所有权的转移
#include 

using namespace std;
int main()
{
	// 在这个范围之外,unique_ptr被释放
	{
		auto i = unique_ptr<int>(new int(10));
		cout << *i << endl;
	}

	// unique_ptr
	auto w = std::make_unique<int>(10);
	cout << *(w.get()) << endl;// 10
	//auto w2 = w; // 编译错误如果想要把 w 复制给 w2, 是不可以的。
	//  因为复制从语义上来说,两个对象将共享同一块内存。

	// unique_ptr 只支持移动语义, 即如下
	auto w2 = std::move(w); // w2 获得内存所有权,w 此时等于 nullptr
	cout << ((w.get() != nullptr) ? (*w.get()) : -1) << endl;     // -1
	cout << ((w2.get() != nullptr) ? (*w2.get()) : -1) << endl;   // 10
	return 0;
}

五、shared_ptr

  • shared_ptr简介:shared_ptr是为了解决auto_ptr在对象所有权上的局限性,在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销
  • shared_ptr原理
    • shared_ptr通过一个引用计数共享一个对象
    • 当引用计数为0时,该对象没有被使用,可以进行析构
#include 

using namespace std;
int main()
{
	// shared_ptr 
	{
		//shared_ptr 代表的是共享所有权,即多个 shared_ptr 可以共享同一块内存。
		auto wA = shared_ptr<int>(new int(20));
		{
			auto wA2 = wA;
			cout << ((wA2.get() != nullptr) ? (*wA2.get()) : -1) << endl;       // 20
			cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl;			// 20
			cout << wA2.use_count() << endl;	// 2
			cout << wA.use_count() << endl;		// 2
		}
		//cout << wA2.use_count() << endl;                                               
		cout << wA.use_count() << endl;		// 1
		cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl;		// 20
		//shared_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared_ptr,
		//	引用计数会 + 1。当一个 shared_ptr 离开作用域时,引用计数会 - 1。
		//	当引用计数为 0 的时候,则 delete 内存。
	}
	
	// move 语法
	auto wAA = std::make_shared<int>(30);
	auto wAA2 = std::move(wAA); // 此时 wAA 等于 nullptr,wAA2.use_count() 等于 1
	cout << ((wAA.get() != nullptr) ? (*wAA.get()) : -1) << endl;          // -1
	cout << ((wAA2.get() != nullptr) ? (*wAA2.get()) : -1) << endl;      // 30
	cout << wAA.use_count() << endl;                                                  // 0
	cout << wAA2.use_count() << endl;                                                // 1
	//将 wAA 对象 move 给 wAA2,意味着 wAA 放弃了对内存的所有权和管理,此时 wAA对象等于 nullptr
	//而 wAA2 获得了对象所有权,但因为此时 wAA 已不再持有对象,因此 wAA2 的引用计数为 1

	return 0;
}
  • shared_ptr的循环引用问题:引用计数会带来循环引用的问题,循环引用会导致堆里的内存无法正常回收,造成内存泄漏

6、C++指针(三):智能指针与内存泄漏_第2张图片

六、weak_ptr

  • weak_ptr简介:weak_ptr被设计为与shared_ptr共同工作,用一种观察者模式工作
  • weak_ptr作用:协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况;观察者意味着weak_ptr只对shared_ptr进行引用,而不改变其引用计数;当被观察的shared_ptr失效后,相应的weak_ptr也相应失效
  • 实例说明
    • BW使用的是weak_ptr,所以test2的tA.use_count()=1
    • 并且查看最后的输出可以发现~A()~B()都没有执行,~AW()~BW()都执行了析构
#include 

using namespace std;

struct B;
struct A {
	shared_ptr<B> pb;
	~A()
	{
		cout << "~A()" << endl;
	}
};
struct B {
	shared_ptr<A> pa;
	~B()
	{
		cout << "~B()" << endl;
	}
};

// pa 和 pb 存在着循环引用,根据 shared_ptr 引用计数的原理,pa 和 pb 都无法被正常的释放。
// weak_ptr 是为了解决 shared_ptr 双向引用的问题。
struct BW;
struct AW
{
	shared_ptr<BW> pb;
	~AW()
	{
		cout << "~AW()" << endl;
	}
};
struct BW
{
	weak_ptr<AW> pa;
	~BW()
	{
		cout << "~BW()" << endl;
	}
};

void Test()
{
	cout << "Test shared_ptr and shared_ptr:  " << endl;
	shared_ptr<A> tA(new A());                                               // 1
	shared_ptr<B> tB(new B());                                                // 1
	cout << tA.use_count() << endl;
	cout << tB.use_count() << endl;
	tA->pb = tB;
	tB->pa = tA;
	cout << tA.use_count() << endl;                                        // 2
	cout << tB.use_count() << endl;                                        // 2
}
void Test2()
{
	cout << "Test weak_ptr and shared_ptr:  " << endl;
	shared_ptr<AW> tA(new AW());
	shared_ptr<BW> tB(new BW());
	cout << tA.use_count() << endl;                                        // 1
	cout << tB.use_count() << endl;                                        // 1
	tA->pb = tB;
	tB->pa = tA;
	cout << tA.use_count() << endl;                                        // 1
	cout << tB.use_count() << endl;                                        // 2
}

int main()
{
	Test();
	Test2();
	return 0;
}

6、C++指针(三):智能指针与内存泄漏_第3张图片

七、引用

  • 引用是什么:是一种特殊的指针,不允许修改的指针
  • 使用指针的坑
    • ①.空指针
    • ②.野指针
    • ③.不知不觉改变了指针的值,却继续使用
  • 使用引用,则可以
    • ①.不存在空引用
    • ②.必须初始化
    • ③.一个引用永远指向它初始化的那个对象
  • 引用的基本使用:可以认为是指定变量的别名,使用时可以认为是变量本身
#include 

using namespace std;

int main()
{
	int x = 1, x2 = 3;
	int& rx = x;
	rx = 2;
	cout << x << endl;//2
	cout << rx << endl;//2
	rx = x2;
	cout << x << endl;//3
	cout << rx << endl;//3
	return 0;
}
  • 使用引用完成交换
#include 
#include 

using namespace std;

// 编写一个函数,输入两个int型变量a,b
// 实现在函数内部将a,b的值进行交换。
void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
void swap2(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

int main()
{
	// 交换变量的测试
	int a = 3, b = 4;
	swap(a, b);
	assert(a == 4 && b == 3);

	a = 3, b = 4;
	swap2(&a, &b);
	assert(a == 4 && b == 3);

	return 0;
}

  • 为什么有了指针还需要引用?:Bjarne Stroustrup解释 —— 为了支持函数运算符重载
  • 为什么有了引用还需要指针?:Bjarne Stroustrup解释 —— 为了兼容C语言

关于函数传递参数类型

  • 对内置基础类型(如int,double等)而言:在函数中传递pass by value(传值)更高效
  • 对OO面向对象中自定义类型而言:在函数中传递pass by reference to const(const的引用)更高效

你可能感兴趣的:(C++知识体系重构,c++,rust,开发语言)