这也是我最近刷面经看到的问题。对于智能指针,其实理解起来还是很容易的,这里我面向初学者,详细介绍一下智能指针的东西,然后咱们一起尝试写一个智能指针,最后讲一下内存泄漏的检测方法,开整。
我之前做开发的同事经常碰到的问题就是内存泄漏,那么什么是内存泄漏呢?在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
内存泄漏的危害真的非常大,它能使软件无可用内存而崩溃。
那么如何检测内存泄漏呢?首先,内存泄漏的表现,有的很严重(在任务管理器中发现内存占用率一直在攀升),当然有的就表现不是那么明显。有什么好的检测方法么?
1.VS下可以直接用crt检测
#define CRTDBG_MAP_ALLOC //放在程序最前
#include
#include
#include
using namespace std;
int main()
{
int *a = new int [10];
int *p = new int[1000];
_CrtDumpMemoryLeaks(); //放在程序最后
system("pause");
return 0;
}
然后程序运行的即使窗口会显示第几次内存分配时忘记释放:
发现原来是第65次和第66次,所以咱们直接加上断点:
#define CRTDBG_MAP_ALLOC
#include
#include
#include
using namespace std;
int main()
{
_CrtSetBreakAlloc(65);
//_CrtSetBreakAlloc(66); //分别取消注释即可触发所有断点。
int *a = new int [10];
int *p = new int[1000];
_CrtDumpMemoryLeaks();
system("pause");
return 0;
}
OK等下就能触发断点,此断点应该是某个new或者malloc操作。
2.Linux下用valgrind
这可是神器,咱们就不在这篇文章中展开了,我单独写个文章解释一下这个东西。
好了刚刚写完了就回来了。继续。
智能指针本来有四类,分别是shared_ptr unique_ptr weak_ptr和auto_ptr,可是c++17取消了最后一个auto_ptr,所以咱们就最好能别用它。
其实简言之,智能指针就两个,分别是可共享的shared_ptr和独占的unique_ptr,至于weak_ptr,那玩意儿纯粹是用来解决shared_ptr的问题才出现的。因此咱们重点介绍前二者,冲冲冲!
这是个独占式的指针对象,在任何时间、资源只能被一个指针占有,当unique_ptr离开作用域,指针所包含的内容会被释放。很显然,unique_ptr类中具有移动构造函数和重载移动赋值运算符,但是没有拷贝构造函数和重载拷贝赋值运算符,对吧,不然爆炸了呀。
我就不用c++14中提到的make_unique来创建unique_ptr了,直接就普普通通的来了。
先看看unique_ptr的定义:
// non-specialized
template <class T, class D = default_delete<T>> class unique_ptr;
// array specialization
template <class T, class D> class unique_ptr<T[],D>;
原来人家给我们设置了默认的删除器,当然咱们自己也可以自己写一个删除器,比如来个简单的:
void myDeletor(int* val)
{
delete val;
std::cout << "balabala" << std::endl;
}
unique_ptr<int, decltype(myDeletor)*> uptrA(new int(10), myDeletor);
接着是简单的一些操作
unique_ptr<int> uptrA( new int );
unique_ptr<int[ ]> uptrB( new int[5] );
//获取管理的指针,建议不要使用
auto ptr = uptrA.get();
//release操作是将uptr管理的内存放弃管理,并返回这个之前被管理的指针,同时将自己管理的内容置为nullptr
uptrA.release();//还是建议不要这么用,不太合适
unique_ptr<int> uptrC(uptrA.release()); //这样就很棒,一去一回
swap(uptrA, uptrB);//交换两个智能指针管理的对象
uptrA.swap(uptrB);
shared_ptr在管理资源时同时会创建一个引用计数,每个shared_ptr对象关联一个共享的引用计数,当某shared_ptr超过作用范围时,会检查引用计数值,若自减操作后refCount==0,则释放管理的资源。
shared_ptr的自定义删除器和unique_ptr稍微有点不同,即它不需要在类模板中指明删除器类型。
创建方法也很简单:
int main() {
shared_ptr<string> pStr = make_shared<string>(10, '1');
cout << *pStr << endl; // 1111111111
int *p = new int(0);
shared_ptr<int> pInt(p);
cout << *pInt << endl; // 0
}
另外允许在创建shared_ptr时传入一个shared_ptr,这样他俩都指向一块内存,且refCount++。
如果用=赋值的话,等号左侧的shared_ptr的refCount会做自减操作,同时会指向右侧的内存以及refCount++。
OK咱们具体的就不多说了,等一下咱们会手写一版咱们自己的shared_ptr。
环形引用这个问题大家肯定很容易就能想到,比如A类里有个private成员,是个shared_ptr,同时还有个B类,B类里也有个private成员,是个shared_ptr。结果巧了,A类的private指向B类成员,同时B类这个成员的private又指向A类。。。。。。
那完了,我想释放哪个都做不到了,因为refCount肯定大于等于2!!!僵硬了。
weak_ptr干的事情,就是去打破这个环,让自己作为环的弱势一方。其实这事儿也有点难受,因为啥呢,是不是有环产生这件事情得咱们程序员自己去判断,因此还是很有风险的。
weak_ptr用的也不多,我就只简单了解到此。
这个其实也是C++提出的一个智能指针类型,不过在C++17中通被废弃了,咱们简单了解一下即可。
想了解为啥auto_ptr被C++17废弃,其实这非常好理解。auto_ptr做的事情其实和unique_ptr差不多,都表达了一种“在同一时间一个资源只能有一个管理对象(智能指针)”,但是auto_ptr的做法实在是让人有点难受。
正常的一个思路是,如果将一个auto_ptr转移送给另外的一个auto_ptr,那么旧的auto_ptr肯定是要卸任的对吧。没错,且事实上,auto_ptr和unique_ptr也都是这么做的。然而不同之处在于,auto_ptr居然在拷贝构造函数和重载拷贝赋值操作符上,将旧的对象管理的东西置为nullptr了。
我也是醉了,请问copy的语义是啥???你把一个东西拷贝到另一个人身上,结果自己居然发生改变了???
很显然这在一些情况下是极其不正确的!
那再来看看咱们的unique_ptr做了啥。人家禁用了拷贝,转而使用移动语义!ohhhhhhhhhhhh
这就非常棒了。
智能指针确实很不错,咱们如果按照人家的规矩办事,方便啊。可是要不按照规矩来,可就很僵硬了:
1.千万不要用相同的指针,去初始化多个智能指针!!!
2.建议少用get操作!!!
3.如果智能指针管理的资源并不是new得到的,请务必给他提个删除器!!!
#pragma once
#ifndef _SMART_POINTER_H_
#define _SMART_POINTER_H_
template <typename T>
class SmartPtr {
private:
T* refValue;
int* refCount;
private:
void accCount()
{
if (refCount != nullptr)
++* refCount;
}
void decCount()
{
if (refCount && -- * refCount == 0)
{
delete refValue;
delete refCount;
refValue = refCount = nullptr;
}
}
public:
//常规构造函数
SmartPtr() :refValue(nullptr), refCount(nullptr) {}
SmartPtr(T* value) :refValue(value) {
refCount = new int( (value == nullptr) ? 0 : 1 );
}
//常规析构函数
~SmartPtr()
{
decCount();
}
//拷贝构造函数
SmartPtr(const SmartPtr& ptr)
{
refValue = ptr.refValue;
refCount = ptr.refCount;
accCount();
}
//重载赋值操作符
SmartPtr& operator=(const SmartPtr& ptr)
{
decCount();
refValue = ptr.refValue;
refCount = ptr.refCount;
accCount();
return *this;
}
//重载解引用操作符
T operator*()
{
return *refValue;
}
//获取引用计数
int getRefCount()
{
return *refCount;
}
};
#endif
OK还是很简单的,注意一下拷贝构造函数和重载赋值运算符即可。