如何回答C++面试中关于智能指针的问题?
1、 什么是智能指针?
2、为什么要用智能指针以及智能指针的原理是什么?
3、 分析下常见的智能指针有哪些?
4、实现一个智能指针呗?(没具体说写哪个,建议默认写:unique_ptr(scoped_ptr))
1、答:智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露(利用自动调用类的析构函数来释放内存)。它的一种通用实现技术是使用引用计数(除此之外还有资源独占,如(auto_ptr),只引用,不计数(weak_ptr))。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
2、答:原因:(1):手动malloc/new出来的资源,容易忘记free/delete;
(2):影响执行流的地方需要注意释放释放资源,容易导致资源泄漏(如free/delete在return之 后)。
(3):中途抛出异常,无法释放资源。如:int*p1=new int;int*p2=new int [10000000];delete p1;
delele [ ] p2;因为p2new的内存比较大,万一new失败,则导致p1永远无法释放。
原理:为了解决以(1)、(2)、(3)上问题,创建一份资源出来的时候,交给一个类对象去管理,当类 对象声明周期结束时,自动调用析构函数释放资源。除此之外,通过运算符重载(重载*和重在-> 等),可以向指针一样使用。
3、常见的智能指针以及解析:
auto_ptr
(1)它是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个拥有者(资源独占)。当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。
(2)auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。(但uniquearray管理的是一段连续的空间,它也是防拷贝的,功能类似于vector。)
(3)auto_ptr不能作为容器对象,因为它不支持拷贝构造与赋值(出错了也不容易发现),STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,那么就会出错。
unique_ptr
它是( C++11引入的,前身是scoped_ptr,scoped_ptr是boost库里的),也不支持拷贝构造和赋值,但比auto_ptr好,直接赋值会编译出错(与auto_ptr最大的不同就是类内私有的声明了拷贝构造函数和赋值运算符重载,是针对auto_ptr的缺点而出现的)。
shared_ptr
C++11或boost的shared_ptr,基于引用计数的智能指针。可随意赋值,直到内存的引用计数为0的时候这个内存会被释放。环状的链式结构可能会形成内存泄露(循环引用)
循环引用问题(常问到哦)
为了解决类似这样的问题,C++11引入了weak_ptr,来打破这种循环引用。
weak_ptr
C++11或boost的weak_ptr,弱引用。 引用计数有一个问题就是互相引用形成环,这样两个指针指向的内存都无法释放。需要手动打破循环引用或使用weak_ptr。顾名思义,weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。
weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
auto_ptr的实现
template<classT>
class Autoptr //采用资源的转移方法管理内存,在它的拷贝构造和赋值运算符引起会出现问题
{
public:
Autoptr(T *p=NULL )
:ptr(p)
{}
Autoptr(Autoptr<T>&ap)
:ptr(ap.ptr)
{
ap.ptr= NULL;//新对象构造成功之后,原来对象指针为NULL;保证free的时候释放1次
}
Autoptr<T>&operator=(Autoptr<T>&ap)
{
if(this!= &ap)
{
if(ap.ptr)
{
deleteptr;
}
ptr= ap.ptr;
ap.ptr= NULL;
}
return*this;
}
T* operator*()
{
return*ptr;
}
T* operator->()
{
returnptr;
}
voidget_ptr()
{
returnptr;
}
~Autoptr()
{
if(ptr)
{
deleteptr;
ptr= NULL;
}
}
private:
T*ptr;
};
voidfuntest()
{
int*p = new int(1);
Autoptr<int>ap1(p); //只是调了构造函数,没有调拷贝构造函数
Autoptr<int>ap2(ap1);//调了拷贝构造函数,ap1=NULL;
Autoptr<int>ap3;
ap3= ap2;//赋值不过去,因为两个对象地址不同,ap2.ptr也不为空,所以调用operator=函数,但是此刻已经释放了
} //ap3的ptr指向的空间,所以没法向ap3赋值
int main()
{
funtest();
system("pause");
return0;
}
unique_ptr的实现
template<classT>
class unique_ptr //前身是scoped_pt,实现粗暴—>禁止转移-->独占资源(只允许一个指针管理资源)
//(禁止调拷贝构造和赋值运算符),它只能管理单个对象
{
public:
unique_ptr (T*p =NULL)
:ptr(p)
{}
T* operator*()
{
return*ptr;
}
T* operator->()
{
returnptr;
}
voidget_ptr()
{
returnptr;
}
~ unique_ptr ()
{
if(ptr)
{
deleteptr;
ptr= NULL;
}
}
private:
unique_ptr (unique_ptr <T>&ap);//禁止拷贝构造
unique_ptr <T>&operator=( unique_ptr <T>&ap1);//禁止赋值
T*ptr;
};
//为什么unique_ptr防拷贝的实现必须是私有的声明?
1、只给共有的声明,不给定义。(用户可以在类外重新给出定义)
2、只给私有的定义(但是类内可以调拷贝构造和赋值运算符)
所以采用私有的声明拷贝构造函数和赋值运算符重载函数实现unique_ptr的防拷贝
shared_ptr的实现
template<classT>
class Share_Ptr
{
public:
Share_Ptr(T*p =NULL)
:_p(p)
,_pcount(NULL)
{
if(NULL != _p)
{
_pcount= new int(1);//构造成功时有一个对象用,初始化为1
}
}
Share_Ptr(const Share_Ptr<T>&ps)
:_p(ps._p)
,_pcount(ps._pcount)
{
if(NULL != _pcount)
{
++*_pcount;
}
}
T* operator*()
{
return*_p;
}
T* operator->()
{
return_p;
}
voidget_ptr()
{
return_p;
}
~Share_Ptr()
{
if(NULL!=_pcount&&0 ==*(--_pcount))
{
delete_pcount;
_pcount= NULL;
delete_p;
_p= NULL;
}
}
private:
T* _p;
int*_pcount;
};
voidFunTest()
{
Share_Ptr<int>ps1(newint);//ps1->_pcount=1;
Share_Ptr<int>ps2(ps1);//ps2->_pcount=2;
Share_Ptr<int>ps3;
ps3= ps2;//ps3->_pcount=2;
}