C++智能指针

1.智能指针的使用场景分析

cpp 复制代码
double Divide(int a, int b) noexcept{
	if (b == 0) {
		throw "Division by zero condition!";
		return -1;
	}
		
	else
		return (double)a / (double)b;
}
void Func() {
	//如果发生除0错误抛出异常,但是array1和array2没有得到释放,所以捕获异常并不处理,而是释放资源后重新抛出,交给外边统一处理,但如果new array2也出现问题呢,还需要一次捕获释放,就很ugly
	int* array1 = new int[10];
	int* array2 = new int[10];
	try {
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (...) {
		cout << "delete[]" << array1 << endl;
		cout << "delete[]" << array2 << endl;
		delete[] array1;
		delete[] array2;
		throw;
	}
	cout << "delete[]" << array1 << endl;
	cout << "delete[]" << array2 << endl;
	delete[] array1;
	delete[] array2;
}
int main() {
	try {
		Func();
	}
	catch (const char* errmsg) {
		cout << errmsg << endl;
	}
	catch (const Exception& e) {
		cout << e.what() << endl;
	}
	catch (...) {
		cout << "Unkonwn Exception" << endl;
	}
	return 0;
}

2.RAII和智能指针的设计思路

  • RAII是Resource Acquisition Is Initialization的缩写,是一种管理资源的类的设计思想,本质是一种利用对象生命周几来管理获取到的动态资源,避免资源泄露或者导致死锁,资源可以是内存,文件指针,网络连接,互斥锁等。RAII在获取资源时把资源委托给一个对象,接着控制对资源的访问,资源在对象的生命周期始终保持有效,在对象析构的时候释放资源,保障资源的正常释放,避免资源泄露
  • 智能指针类除了满足RAII的设计思路,还要方便资源的访问,所以智能指针类还会像迭代器一样,重载operator*/->/[]等运算符
cpp 复制代码
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{
	}

	~SmartPtr() {
		cout << "delete[]" << endl;//便于观察是否完成析构
		delete[] _ptr;//有缺陷,因为不一定new的是数组
	}

	//重载运算符,模拟指针的行为,方便访问资源
	T operator[](int pos) {
		return _ptr[pos];
	}

	T operator*() {
		return *_ptr;
	}

	T* operator->() {
		return _ptr;
	}

private:
	T* _ptr;
};
double Divide(int a, int b){
	if (b == 0) {
		throw "Division by zero condition!";
		return -1;
	}
		
	else
		return (double)a / (double)b;
}
void Func() {
	/*int* array1 = new int[10];
	SmartPtr<int> p1 = array1;*/ //可以进行赋值
	SmartPtr<int> p1  = new int[10];//也可以在构造的时候直接申请资源
	//交给SmartPtr这个类去管理,一旦抛异常,导致Func后续代码不再执行,Func函数栈帧销毁,定义的对象p1和p2销毁,析构的时候会处理资源
	int* array2 = new int[10];
	SmartPtr<int> p2(array2);
	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}
int main() {
	try {
		Func();
	}
	catch (const char* errmsg) {
		cout << errmsg << endl;
	}
	catch (const Exception& e) {
		cout << e.what() << endl;
	}
	catch (...) {
		cout << "Unkonwn Exception" << endl;
	}
	return 0;
}
cpp 复制代码
//可以看到先进行析构,也就是清理函数栈帧,才捕捉的异常
1 0
delete[]
delete[]
Division by zero condition!

3.C++标准库智能指针的使用

  • C++标准库中的指针都在< memory>这个头文件下面,包含< memory>可以使用;智能指针有多重,出了weak_ptr都符合RAII和像指针一样访问的行为,原理上而言主要是解决智能指针拷贝时的思路不同
  • 智能指针的拷贝和vector的拷贝不一样,因为vector和string都是资源的拥有者,vector进行拷贝是说开辟一块空间,把你的元素依次复制下来;但是智能指针是托管,拷贝智能指针不是要把你的资源拷贝一份,而是管理权的问题,auto_ptr是管理权转移,unique_ptr直接不支持拷贝,shared_ptr是共享管理权
  • auto_ptr是C++98设计出来的指针,特点是拷贝是把被拷贝对象资源的管理权转移给拷贝对象,问题是被拷贝对象悬空,访问报错,C++11设计出新的智能指针后, 强烈建议不要使用auto_ptr,C++11出来之前很多公司也是明令禁止使用的
cpp 复制代码
	//auto_ptr<Date> p2 = new Date; 不支持隐式类型转换,因为构造加了expilicit,只允许显式构造,也就是auto_ptr<Date> p2(new Date),也就是auto_ptr<Date> p2(Date*);
	//但上面写法一般是隐式类型转换,右边生成临时变量,Date* p,p去赋值p2,编译器尝试调用auto_ptr<Date> p2 = auto_ptr<Date>(Date*);不允许隐式构造,必须有名有姓
  • unique_ptr是C++11设计出来的,不支持拷贝,只支持移动构造,适用于不需要拷贝的场景

  • shared_ptr对于拷贝使用引用计数的方法,最后一个管理资源的对象释放资源

cpp 复制代码
class Date {
public:
	Date(int year=1,int month=1,int day=1)
		:_year(year),
		_month(month),
		_day(day)
	{}
	~Date() {
		cout << "~Date()" << endl;
	}
	int _year;
	int _month;
	int _day;
};
int main() {
	auto_ptr<Date> p1(new Date);
	auto_ptr<Date> p2(p1);
	//p1->_day = 1; p1已悬空,不能访问空

	unique_ptr<Date> p3(new Date);
	//unique_ptr<Date> p4(p3); 不支持
	unique_ptr<Date> p4(move(p3));

	shared_ptr<Date> p5(new Date);
	shared_ptr<Date> p6(p5);
	shared_ptr<Date> p7(p6);
	cout << p5.use_count() << endl;//有几个shared_ptr对象在管理p5管理的资源
	cout << p5.unique() << endl;//是否p5是所管理资源的唯一管理者
	
	p7.reset();//如果p7所管理资源的use_count--,p7悬空
	//shared_ptr重载了operator bool,可以当指针用,相当于if(p5.operator bool()),如果p5不为空,则为true
	if (p5) {
		cout << p5.use_count() << endl;
	}

	return 0;
}
cpp 复制代码
int main() {
	shared_ptr<Date> sp1(new Date);
	shared_ptr<Date> sp2(sp1);
	shared_ptr<int> sp4(sp1,&sp1->_year);//
	sp4.reset();//sp1的引用计数-1
	return 0;
}



大概意思是说,与operator <重载不同的是,owner_less考虑的不是存储的指针,而是实际上管理资源的指针,并且在管理资源的指针不同且指针小于x(小于一般比较的是地址,没有传入仿函数,该比较规则不会改变,但是一般不这样用,而且按地址比大小没啥意义)才会返回true,也就是说在管理相同资源的情况下返回false(此时不区分操作数顺序)

只有a和b拥有同一资源,下面的表达式结果才是true,用于去重,只有结果是false,map/set才会按照key比较

cpp 复制代码
( !a.owner_before(b) && !b.owner_before(a) )

而正如operator<是为Less服务的,owner_before是为owner_less服务的,owner_less从本质上说是仿函数,用于set/map去重,但是owner_less有自己的逻辑要实现(主要是实现同一类型或者不同类型指针的比较),为避免本身太重,就把基于owner的比较交给了owner_before

owner_less是模版,但是模版本身不用于比较,特化的两个template<class T> struct owner_less <shared_ptr<T>>才是真正用于比较的template<class T> struct owner_less <weak_ptr<T>>,而用谁特化,在operator()的操作数中至少有一个操作数是该类型的

owner_less模仿二元函数(两个参数一般是同类型),但支持额外的重载(不同类型的参数)

我们从例子本身可以看到owner_less本身是作为仿函数传递的,而默认就是根据存储💾的指针来比较的 ,没有提供基于所有权的==,只提供基于所有权的<,所以使用owner_before和owner_less而不是 = =

cpp 复制代码
int main() {
    struct Base1 { virtual ~Base1() = default; int _a = 1; };
    struct Base2 { virtual ~Base2() = default; int _b = 2; }; 
    struct Derived : Base1, Base2 {};

    auto d = make_shared<Derived>();
    shared_ptr<Base2> b = d;  // 这里地址 一定 不同!

    cout << (b.get() == d.get()) << endl;  // 输出 1,比较托管对象是否相同
    cout << "d.get() = " << d.get() << endl;
    cout << "b.get() = " << b.get() << endl;
    cout << (static_cast<void*>(b.get()) == static_cast<void*>(d.get())) << endl;

    return 0;
}
cpp 复制代码
1
d.get() = 010AFCC4
b.get() = 010AFCCC
0


make_shared是函数模版,模版参数T是要创建对象的类型,参数包用于传递给T的构造函数

cpp 复制代码
int main() {
	shared_ptr<int> p = make_shared<int>(10);
	shared_ptr<pair<int,int>> p1 = make_shared<pair<int,int>>(10,20);
	return 0;
}
cpp 复制代码
template<class T>
struct Del {
	void operator()(T* ptr) {
		delete[] ptr;
	}
};
int main() {
	//方法一
	/*auto del = [](int* ptr) {
		delete[] ptr;
		};
	shared_ptr<int> sp1(new int[10], del);*/
	
	//方法二
	/*shared_ptr<int> sp1(new int[10], [](int* ptr) {
		delete[] ptr;
		});*/
	
	//方法三
	//shared_ptr<int[]> sp1(new int[10], Del<int>());

	//C++17及之后支持数组特化版
	shared_ptr<int[]> sp1(new int[10]);

	//shared_ptr管理文件资源
	shared_ptr<FILE> sp1(fopen("Test.txt", "r"), [](FILE* fp) {if(fp)fclose(fp); });
	return 0;
}
cpp 复制代码
template<class T>
struct Del {
	void operator()(T* ptr) {
		delete[] ptr;
	}
};

struct Fclose {
	void operator()(FILE* fp) {
		if (fp) fclose(fp);
	}
};

int main() {
	//方法一:仿函数
	/*unique_ptr<int> up1(new int);
	unique_ptr<int[], Del<int>> up2(new int[10],Del<int>());*/
	
	//行不通,因为不同位置的lambda即使内容一样,也会是不同的类型
	//unique_ptr<int[], decltype([](int* ptr) {delete[] ptr; }) > up2(new int[10], [](int* ptr) {delete[] ptr; });

	//只用一次lambda,把lambda类型保存下来
	auto del = [](int* ptr) {
		delete[] ptr;
		};
	unique_ptr<int[], decltype(del) > up2(new int[10], del);
	//C++20之后支持
	unique_ptr<int[], decltype(del) > up3(new int[10]);
	
	//C++11之后支持unqiue_ptr数组特化,在模版类型传T[],直接调用[]的删除器,不需要手动传入
	unique_ptr<int[]> up4(new int[10]);

	
	
	//unique_ptr管理文件资源
	unique_ptr<FILE, Fclose> uq(fopen("Text.txt", "r"), Fclose());
	
	//只允许传仿函数类的对象
	/*unique_ptr<FILE, Fclose> uq(fopen("Text.txt", "r"), [](FILE* fp) {
		if (fp) fclose(fp);
		});*/
	return 0;
}
  • weak_ptr是C++11设计出来的智能指针,不同于上面的指针指针,不支持RAII,用于解决shared_ptr循环引用导致的内存泄漏问题。

4.模拟实现

4.1 auto_ptr

虽然auto_ptr已经被C++11遗弃,但是模拟实现的过程自有其价值

很多细节需要从官网的说明下手

在构造的时候,p=0说明支持空指针进行构造

析构的时候用的是delete说明析构的不是数组,而是单个对象

cpp 复制代码
namespace diy {
	template<class T>
	class auto_ptr {
	public:
		auto_ptr(T* ptr=nullptr)//不能是const T*,因为不能const T*->T*
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& ap)//不能是const,因为const的_ptr不能转为_ptr
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap) {
			if (this != &ap) {
				if(_ptr)
					delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;//不能是ap,连续赋值返回左操作数的引用
		}
		~auto_ptr() {
			if(_ptr)
				delete _ptr;
		}
		T* get() {
			return _ptr;
		}
		
		T* operator->() {
			return _ptr;
		}
		T& operator*() {
			assert(_ptr);
			return *_ptr;
		}
	private:
		T* _ptr;
	};
}

4.2 unique_ptr

unique_ptr在模拟实现的时候两个模版参数,一是管理资源的类型,而是删除器的类型(删除器用于管理数组,文件等资源,或者用malloc,celloc等申请资源,默认使用delete释放单个对象)

在实现默认删除器的时候,对数组[]的删除器进行特化,那么资源类型为T[]的时候,不需要传入删除器,编译器直接调用delete[]

cpp 复制代码
namespace diy {
	template<class T,class Del=default_delete<T>>
	class unique_ptr {
	public:
		using MyUniquePtr = unique_ptr<T, Del>;
		explicit unique_ptr(T* ptr=nullptr,Del del=Del())
			:_ptr(ptr),_del(del)
		{}
	
		unique_ptr(const MyUniquePtr& up) = delete;
	
		MyUniquePtr& operator=(const MyUniquePtr& up) = delete;
		unique_ptr(MyUniquePtr&& up) noexcept//移动构造,把你的资源拿过来
			:_ptr(up._ptr),_del(std::move(up._del))
		{
			up._ptr = nullptr;
		}
		
		void reset(T* ptr=nullptr) {
			if (_ptr) 
				_del(_ptr);
			_ptr = ptr;
		}
	
		void swap(MyUniquePtr& up) {
			std::swap(_ptr, up._ptr);
			std::swap(_del, up._del);
		}
		MyUniquePtr& operator=(MyUniquePtr&& up)noexcept{
			if (this != &up) {
				if (_ptr)
					_del(_ptr);
				_ptr = up._ptr;
				_del = std::move(up._del);
				up._ptr = nullptr;
			}
			return *this;
		}
	
		T* get() {
			return _ptr;
		}
	
		T* operator->() {
			return _ptr;
		}
		T& operator*() {
			assert(_ptr);
			return *_ptr;
		}
		
		~unique_ptr() {
			if (_ptr) {
				cout << "~unique_ptr" << endl;
				_del(_ptr);
			}
		}
	
	private:
		T* _ptr=nullptr;
		Del _del{};
	};
}

4.3 shared_ptr

4.3.1 模拟实现shared_ptr

cpp 复制代码
namespace diy {
	template<class T>
	class shared_ptr {
	public:
		shared_ptr(T* ptr=nullptr)
			:_ptr(ptr),_pcount(new int(1))
		{}

		//没有用,因为new int[10]识别为int* 走的还是默认构造,如果要支持int[]需要单独写一个类
		//template<class D=default_delete<T>>
		//shared_ptr(T* ptr,const D& del=D())//del没有默认值的情况下,ptr不能有默认值
		//	:_ptr(ptr), _pcount(new int(1)),_del(del)
		//{}

		template<class D>
		shared_ptr(T* ptr,D del)//del没有默认值的情况下,ptr不能有默认值
			:_ptr(ptr), _pcount(new int(1)),_del(del)
		{}
	
		//拷贝
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr), _pcount(sp._pcount),_del(sp._del)
		{
			++(*_pcount);
		}
		void release() {
			if (--(*_pcount) == 0) {
				_del(_ptr);
				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}
		//赋值
		shared_ptr<T>& operator=(const shared_ptr<T>& sp){
			if (this != &sp) {
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_del = sp._del;
				++(*_pcount);
			}
			return *this;
		}

		T* get() const{
			return _ptr;
		}

		T* operator->() {
			return _ptr;
		}
		T& operator*() {
			assert(_ptr);
			return *_ptr;
		}

		~shared_ptr() {
			release();
		}
	private:
		T* _ptr;
		int* _pcount;//因为引用计数是管理同一资源的shared_ptr共享的,所以不能直接是成员变量int _pcount;这是属于每个对象的,而如果使用静态的,那是属于整个类的,也不太合适
		std::function<void(T*)> _del = [](T* p) {delete p; };
	};

	template<class T>
	class shared_ptr<T[]> {
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr), _pcount(new int(1))
		{}

		//拷贝
		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr), _pcount(sp._pcount)
		{
			++(*_pcount);
		}
		//赋值
		shared_ptr& operator=(const shared_ptr& sp) {
			if (this != &sp) {
				if (--(*_pcount) == 0) {
					_del(_ptr);
					delete _pcount;
				}

				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
				return *this;
			}
		}

		T* get() {
			return _ptr;
		}

		T* operator->() {
			return _ptr;
		}
		T& operator*() {
			assert(_ptr);
			return *_ptr;
		}
		T& operator[](int i) {
			return _ptr[i];
		}

		~shared_ptr() {
			if (--(*_pcount) == 0) {
				_del(_ptr);
				delete _pcount;
			}
			_ptr = nullptr;
		}
	private:
		T* _ptr;
		int* _pcount;//因为引用计数是管理同一资源的shared_ptr共享的,所以不能直接是成员变量int _pcount;这是属于每个对象的,而如果使用静态的,那是属于整个类的,也不太合适
		std::function<void(T*)> _del = [](T* p) {delete[] p; };
	};
}

4.3.2 循环引用

shared_ptr在大多数情况下管理资源非常合适,支持RAII,支持拷贝,但有一个大问题,就是循环引用导致资源没释放

cpp 复制代码
struct ListNode {
	ListNode(int val)
		:_val(val),_prev(nullptr),_next(nullptr)
	{}
	int _val;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
	~ListNode() {
		cout << "~ListNode" << endl;
	}
};
int main() {
	shared_ptr<ListNode> p1(new ListNode(1));
	shared_ptr<ListNode> p2(new ListNode(2));
	p1->_next = p2;
	p2->_prev = p1;
	
	return 0;
}

运行代码,没有输出,说明没有析构

那么右边节点什么时候析构呢?需要左边节点的_next析构,左边节点_next什么时候析构呢?需要左边节点析构,左边节点什么时候析构呢?需要右边节点的 _prev析构,右边节点的_prev只有右边节点析构之后才会析构,右边节点什么时候析构呢?回到了最开始,就是兜圈子,最后没释放资源

这时候,weak_ptr上场了,可以简单理解为weak_ptr不增加引用计数(但实际上没有增加shared_ptr的引用计数,weak_ptr有自己的引用计数)

把_prev或者_next改为weak_ptr,shred_ptr可以构造weak_ptr,但不支持nullptr构造weak_ptr,这样

cpp 复制代码
struct ListNode {
	ListNode(int val)
		:_val(val)
	{}
	int _val;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode() {
		cout << "~ListNode" << endl;
	}
};
int main() {
	shared_ptr<ListNode> p1(new ListNode(1));
	shared_ptr<ListNode> p2(new ListNode(2));
	p1->_next = p2;
	p2->_prev = p1;
	
	return 0;
}

首先p1出现后,接管1号节点,1号节点的shared_count为1,p2出现,接管2号节点,p2的shared_count为1,p1->_next=p2,2号节点weak_count为1,p2->_prev=p1,p1的weak_count为1,p2先销毁,2号节点的shared_count=0,释放二号节点,p2的_prev也要销毁,此时节点1的weak_count=0,但是节点1的shared_count不为0,所以不释放1号节点的控制块;接着,p1销毁,1号节点的shared_count为0,释放1号节点,且此时1号节点的weak_count为0,释放1号节点控制块;同时1号节点的_next析构,导致2号节点的weak_count为0,释放二号节点的控制块

节点的shared_count为0,释放资源

当且仅当shared_count为0且weak_count为0才释放控制块!

但是下面的weak_ptr实现的极为简单,STL库里一般是把引用计数单独搞成一类,weak_ptr和shared_ptr都存储指向控制块的指针,控制块也是new出来的

4.4 weak_ptr

weak_ptr不支持RAII,不支持访问资源,专门绑定shared_ptr,不增加强引用计数,作为一些场景的辅助管理

因为不参与资源管理,所以没有重载operator*,operator->等,因为资源释放和弱引用计数没有关系,如果强引用计数为0,释放资源,那weak_ptr再去访问会出问题

weak_ptr支持expired检查指向的资源是否过期,use_count获取shared_ptr的引用计数;可以调用lock返回一个管理资源的shared_ptr,如果资源已释放,返回的是空对象,如果没有释放,通过返回的shared_ptr访问资源是安全的

cpp 复制代码
template<class T>
class weak_ptr {
public:
	weak_ptr(const shared_ptr<T> sp)
		:_ptr(sp.get())
	{
	}
	weak_ptr<T>& operator=(const shared_ptr<T>& sp) {
		_ptr = sp.get();
		return *this;
	}
private:
	T* _ptr = nullptr;
};

测试

cpp 复制代码
int main() {
	shared_ptr<string> p1(new string("111"));//p1指向资源
	shared_ptr<string> p2(p1);//p2指向资源,引用计数为2
	weak_ptr<string> wp = p1;
	cout << wp.use_count() << endl;//本质上有多少个shared_ptr共管资源,weak_ptr不增加强引用计数,所以打印结果是2
	cout << wp.expired() << endl;//指向的资源被销毁为1
	p1 = make_shared<string>("222");
	cout << wp.use_count() << endl;//p1指向其他资源,引用计数为1
	cout << wp.expired() << endl;
	p2 = make_shared<string>("333");
	cout << wp.use_count() << endl;//强引用计数为0,资源释放,但是弱引用计数为1,强引用计数没释放,wp过期(强引用计数为0)
	cout << wp.expired() << endl;
	cout << "******" << endl;

	wp = p1;//wp又指向p1指向的资源,此时之前的控制块销毁
	//auto p3 = wp.lock();
	shared_ptr<string> p3 = wp.lock();//通过weak_ptr来访问资源,一定要先lock并用shared_ptr接受
	//此时p3共同管理p1管理的资源,强引用计数为2
	p1.reset();//p1不再管理,强引用计数为1
	cout << wp.use_count() << endl;
	cout << wp.expired() << endl;
	*p3 += "###";
	cout << *p3 << endl;
	return 0;
}

5.shared_ptr的线程安全问题

  • shared_ptr的引用计数对象在堆上,如果多个shared_ptr对象在多选线程中,进行shared_ptr的拷贝、析构时会访问修改引用计数,存在线程安全问题,所以shared_ptr引用计数需要加锁或者原子操作保证线程安全
  • shared_ptr指向的对象也是有线程安全问题,但shared_ptr本身无法解决,外层shared_ptr使用者进行线程安全的控制。

这段代码直接崩了,把_pcount改为atomic< int>*才能跑,或者使用互斥锁加锁

cpp 复制代码
struct AA {
	int _a1 = 0;
	int _a2 = 0;

	~AA() {
		//cout << "~AA" << endl;
	}
};
#include <mutex>
int main() {
	diy::shared_ptr<AA> p(new AA);
	const size_t n = 100000;
	mutex mtx;
	auto func = [&]() {
		for (size_t i = 0; i < n; i++) {
			diy::shared_ptr<AA> copy(p);
			unique_lock<mutex> lk(mtx);
			copy->_a1++;
			copy->_a2++;
		}
		};
	thread t1(func);
	thread t2(func);

	t1.join();
	t2.join();

	cout << p->_a1 << endl;
	cout << p->_a2 << endl;

	cout << p.use_count() << endl;
	return 0;
}

6.C++11和Boost中智能指针的关系

  • Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,Boost社区建立的初衷之一就是为C++的标准化工作提供可供参考的实现,Boost社区的发起人Dawes就是C++标准委员会的成员之一。在Boost库的开发中,Boost社区在这个方向取得了丰硕的成果,C++11及之后的新语法和库很多都是从Boost中来的。
  • C++98产生了第一个智能指针auto_ptr
  • C++Boost给出了更实用的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等
  • C++TR1,引入shared_ptr等,但TR1不是标准版
  • C++11引入了unique_ptr,shared_ptr以及weak_ptr,unique_ptr对应Boost中的scoped_ptr,这些智能指针参考Boost中的实现。

从设计的角度看,C++标准很多内容来自Boost库,比如shared_ptr,但是进行了一定的改进,比如shared_ptr和shared_array合并成了shared_ptr,构造的时候传入删除器来实现是否数组对象,而且C++标准库也支持文件资源的管理,相当于对其进行了合并和功能扩展

7.内存泄漏

7.1概念和危害

  • 内存泄露指因为疏忽或者错误造成程序未能释放已经不再使用的内存,一般是忘记释放或者发生异常,释放程序未能执行导致的。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,造成内存的浪费
  • 普通程序运行一会儿就结束了,有内存泄漏,但是进程结束,页表映射关系解除,物理内存正常释放。但是比如长期运行的服务器、操作系统、客户端等,不断出现内存泄露导致可用内存不断减少,功能响应越来越慢,最终卡死。
cpp 复制代码
int main() {
	//申请一个G未释放,程序多次运行没什么影响
	//程序马上结束,进程结束各种资源也就回收了
	char* p = new char[1024 * 1024 * 1024];//1GB
	cout << (void*)p << endl;
	return 0;
}

7.2 如何检测(了解)

但问题是,编译器一般是运行时检测,也就是说必须在测试的时候把每个功能都调用,而且有的内存泄露可能就是每次一点点,可能要很长时间才能表现出来问题

7.3 如何避免

  • 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记得匹配的去释放,如果碰上异常,可能就算写了释放也会出问题,需要智能指针。
  • 尽量用智能指针管理资源,如果场景特殊,使用RAII思想自己造轮子。
  • 定期使用内存泄露工具检测,尤其项目快上线前,但工具也不一定靠谱而且收费
  • 解决方案:事情预防(智能指针等)+事后查错(检测工具)
相关推荐
OYangxf2 小时前
基于epoll的单线程Reactor:Tinyredis的网络层实现
c++·redis
yinbinggang3 小时前
vmware安装虚拟机
c++
小小de风呀4 小时前
de风——【从零开始学C++】(三):类和对象(中序):默认成员函数全解析
开发语言·c++
迷途之人不知返4 小时前
vector的模拟实现
c++
浅念-4 小时前
分治算法专题|LeetCode高频经典题目详细题解
数据结构·c++·算法·leetcode·职场和发展·排序·分治
H Journey4 小时前
C++ 性能瓶颈分析与优化
c++·性能优化·gprof·perf·valgrind·瓶颈分析
熬夜敲代码的猫5 小时前
C++继承:让你从入门到深入
c++·算法·继承
txz20355 小时前
2,使用功能包组织C++节点
开发语言·c++·ros
谭欣辰5 小时前
C++ 哈希表详解
c++·算法·哈希算法·散列表