Q、智能指针都有哪些呢?
C++11之前:auto_ptr
C++11:unique_ptr share_ptr weak_ptr
Boost库:scrope_ptr share_ptr weak_ptr
Q、为何选择智能指针?智能指针解决了什么问题?
智能指针的出现肯定是与普通指针存在的缺点相对应的,所有的事物都是在进步的嘛!想想普通指针,容易发生的两个缺点:
(1)、程序员自己忘了写释放资源的代码;
(2)、程序逻辑上没有运行到释放代码的地方。
这两个方面都会导致内存泄漏。因此,很容易断定智能指针解决了自动释放资源的问题。
那么,能够自动释放资源是怎么做到的呢?我们会想到析构函数,对于一个对象,都将在过期的时候系统会帮忙调用析构函数进行释放它的内存。考虑到内置类型的指针没有析构函数,所以unique_ptr、share_ptr、weak_ptr内部都是用模板(适应不同类型的需求)来实现,进行封装成类对象指针,并在析构函数里写delete语句删除指向的内存。
看下面栗子:
void func1(string &str)
{
/*当发生异常时,delete不会被执行,导致内存泄漏。
可以在抛出异常之前写上delete,可是人总是会犯错,这样并不是明智之举。
*/
//string *ps=new string(str);//#1
//if(ps==NULL)
// throw exception();
//str=*ps;
//delete ps;
//return;
auto_ptr<string> ps(new string(str));//将ps替换为了指向string类的智能指针对象
//将#1句改为这个就不需要delete了。
}
//当该函数终止时(不管是正常终止还是异常终止),本地变量都将自动从栈内存中删除(包括ps占据的内存).
Q、智能指针怎么用呢?
(1)、头文件:#include<memory> 使用Boost库里边的智能指针应该加上#include<boost>
(2)、以auto_ptr为例,查看其定义
发现以指针作为参数的构造函数有explicit修饰,其余unique_ptr、share_ptr、weak_ptr一样。所以在使用时不能自动将指针转换为智能指针对象,需要显示调用。看栗子:
int main()
{
shared_ptr<int> p1;
int *p_reg1=new int;
//pd1=p_reg1;//error 构造函数不允许隐式调用
p1=shared_ptr<int>(p_reg1);//ok 显示调用构造函数
int *p_reg2=new int;
//shared_ptr<int> pshare=p_reg2;//error 隐式调用拷贝构造函数
shared_ptr<int> pshare(p_reg2);//ok 显示调用
//!!!!对三种指针都应该避免的一点
//string str("hello");
//shared_ptr<string> psh(&str);//error
//因为当psh过期时,程序将delete指向非堆内存,程序奔溃
return 0;
}
使用一下它呗~
class CGoods
{
public:
CGoods(const string name):_name(name){cout<<"CGoods(string)"<<endl;}
~CGoods(){cout<<"~CGoods()"<<endl;}
void show()const
{
cout<<"name::"<<_name<<endl;
}
private:
string _name;
};
int main()
{
auto_ptr<CGoods> good1(new CGoods("mian bao"));
good1->show();
shared_ptr<CGoods> good2(new CGoods("niu nai"));
good2->show();
unique_ptr<CGoods> good3(new CGoods("han bao"));
good3->show();
return 0;
}
打印
Q、C++98中智能指针唯一的选择就是auto_ptr,在之后auto_ptr有什么缺陷?为什么要摒弃它?
缺陷:来源于auto_ptr相互赋值时,对于浅拷贝的处理上。在前面已经讲了,智能指针已经变为指向某一类型的智能指针对象,那相互赋值,自然而然就有浅拷贝的现象。
引入对于浅拷贝的处理方式:
(1)、通常有浅拷贝现象时我们要求进行实现深拷贝,对于自定义类型时,程序员要自己实现赋值运算符重载函数。看例1:
class A
{
public:
A(int a,char *p):ma(a)
{
mp=new char[strlen(p)+1];
strcpy(mp,p);
cout<<"A(){}"<<endl;
}
~A()
{
delete mp;
cout<<"~A(){}"<<endl;
}
/*A& operator=(const A& rhs)//自己实现赋值运算符重载函数
{
if(&rhs!=this)
{
delete mp;
mp=new char[strlen(rhs.mp)+1];
strcpy(mp,rhs.mp);
}
return *this;
}*/
private:
int ma;
char* mp;
};
int main()
{
A a(1,"hahaha");
A b(2,"heiheihei");
a=b;//如果用系统默认的赋值运算符重载函数,程序奔溃!!!!
return 0;
}
(2)、建立所有权的概念。对于特定的对象,只有一个指针可拥有,这样只有拥有对象的指针对其有释放的权利。让赋值运算符转让所有权,这就是unique_ptr和auto_ptr的策略。但是unique_ptr比较严格一点。
写个例子试一下:
auto_ptr<string> ps(new string("I am hagury"));
auto_ptr<string> eat;
eat=ps;
运行,没出现问题!什么情况,调试看了一下,发现赋值之后,ps变为NULL,在C++中对悬挂指针的释放是正确的,所以运行成功了。但对于例1中调用默认的赋值运算符重载程序奔溃属于两次删除同一个对象,在C++中两次删除同一个对象是未定义的。如这样的肯定出错!
int *p=new int (10);
auto_ptr<int> p1(p);
auto_ptr<int> p2(p);
额。。。接着再写:
auto_ptr<string> ps(new string("I am hagury"));
auto_ptr<string> eat;
eat=ps;
cout<<*ps<<"";//打印,出错!
程序奔溃!!!打印*ps,访问了NULL指针,出错。所以一句话,摒弃auto_ptr就是因为auto_ptr有潜在内存奔溃的原因。更隐蔽的就是auto_ptr作为函数参数按值传递时,因为在函数调用的过程中在函数的作用域中会出现一个局部对象来接受传入的auto_ptr(拷贝构造),这样,传入的实参就失去了其原来对象的所有权,当函数没返回值是,就有潜在的问题了。因此在使用auto_ptr时一定要注意。
(3)、创建智能更高的指针,跟踪引用特定对象的智能指针数。也就是引用计数。也就是share_ptr的设计策略。赋值时引用计数加一,指针过期时,引用计数减一,直到引用计数减为0时调用delete进行释放所占的额外资源。
Q、unique_ptr相对于auto_ptr的优越性?
(1)、unique_ptr在编译期间避免了auto_ptr出现的问题,更加安全。当出现浅拷贝的时候,智能指针unique_ptr和auto_ptr都采用所有权转让的方式,(效率比自己实现拷贝构造高),必然很好,但是转让之后就立即使用转让之前的指针,这将是件坏事,对于auto_ptr程序崩溃,而unique_ptr只是编译出现错误。
/*unique_ptr<string> pu1(new string("hello"));
unique_ptr<string> pu2;
pu2=pu1;*/
//error 编译期间报错
auto_ptr<string> pu3;
pu3=auto_ptr<string>(new string("world"));
//ok 编译期间正常
(2)、当试图将一个unique_ptr赋值给另一个时,如果原unique_ptr是个临时右值,编译器允许这样做;当原unique_ptr将存活一段时间时,编译器将禁止这么做。
int main()
{
unique_ptr<string> ps1,ps2;
/*通过func2返回一个临时unique_ptr,
然后ps1接管后临时的unique_ptr被销毁
ps1没有机会使用unique_ptr来访问有效的数据
这个赋值是没有任何问题的,所以没有理由禁止这种操作。!!!
*/
ps1=func2("unique only");//ok
ps1=ps2;//编译出错,可使用move方法将原来的指针将所有权转让给ps1
ps1=move(ps2);
ps2=func2("right");//ok 可对其重新赋值
return 0;
}
像这种情况,也同样出现在用unique_ptr给shared_ptr赋值时,如下
unique_ptr<int> up(unique_ptr<int> (new int(66)));
shared_ptr<int> sp(up);//编译出错 error
shared_ptr<int> sp(unique_ptr<int> (new int(88)));//OK
Q、auto_ptr能否被用在STL标准容器里?
答案是否定的,STL中使用赋值和复制时错误,因为auto_ptr不具有值语义。
值语义:A为一个自定义类型
A a1;
A a2(a1);
A a3;
a3=a1;//以上都是前提
a2==a1;//结果
a3==a1;//结果
auto_ptr很明显不符合。在STL中频繁的使用赋值和复制时推荐使用shared_ptr(STL和Boost库里的都可以)。如果不进行赋值或者复制时,可以将unique_ptr存储到STL容器中(auto_ptr也可以,但是优先选择unique_ptr)。
//如果不用引用接受时,编译将发现错误使用unique_ptr的企图
void show(unique_ptr<int> &p1)
{
cout<<*p1<<" ";
}
int main()
{
vector<unique_ptr<int>> vec(10);
for(int i=0;i<10;++i)
{
vec[i]=unique_ptr<int> (new int(rand()%100));
}
vec.push_back(unique_ptr<int> (new int(555)));//ok push_back返回的是临时量
for_each(vec.begin(),vec.end(),show);
//sort(vec.begin(),vec.end());//error
//copy(vec.begin(),vec.end(),ostream_iterator<unique_ptr<int>>(cout," "));//error
return 0;
}
Q、不应该用auto_ptr来管理一个数组。
看auto_ptr的定义,知道auto_ptr的析构函数中删除是delete而不是delete[].
int main()
{
auto_ptr<string> song[5]={
auto_ptr<string>(new string("just the way you are")),
auto_ptr<string>(new string("angle")),
auto_ptr<string>(new string("give you my world")),
auto_ptr<string>(new string("pricious")),
auto_ptr<string>(new string("earth song"))
};//定义一个数组
unique_ptr<string> rap;//对于#1,编译期间出错,unique_ptr也采用转让所有权模式,但在使用时程序不会等到运行时出错而是在编译期间就报错。
//shared_ptr<string> rap;//对于#1,正确。它采用引用计数的方式,在释放时会判断引用计数的大小,不会出现多次释放同一块内存空间。
auto_ptr<string> rap;//对于#1,运行期间出错
rap=song[2];//#1
//打印时程序会被奔溃,因为song[2]将所有权转让给了rap
//此时song[2]不再引用“sngle”字符串从而变为空指针。
cout<<"english song from my idol JasonZhang:"<<endl;
for(int i=0;i<5;++i)
{
cout<<*song[i]<<endl;
}
cout<<"the rap is :"<<*rap<<endl;
}
Q:智能指针的交叉引用?
在类A和类B中出现了两个强类型指针。如下:
class B;
class A
{
public:
shared_ptr<B> _bptr;
};
class B
{
public:
shared_ptr<A> _aptr;
};
int main()
{
shared_ptr<A> aptr(new A());
shared_ptr<B> bptr(new B());
aptr->_bptr=bptr;
bptr->_aptr=aptr;
return 0;
}
当程序结束时,引用计数分别减一,不为0;程序结束,内存泄漏
解决方法:将类A和类B中的强类型指针转换成weak_ptr。Weak_ptr指针也采用的是引用计数,但是它实质上不加,只是实时的监视着观察着引用计数。可以从结果看出内存未泄漏,如下:
class B;
class A
{
public:
weak_ptr<B> _bptr;
};
class B
{
public:
weak_ptr<A> _aptr;
};
int main()
{
shared_ptr<A> aptr(new A());
shared_ptr<B> bptr(new B());
aptr->_bptr=bptr;
bptr->_aptr=aptr;
return 0;
}
Q:怎么选择智能指针?
(1)、如果程序要使用多个指向同一个对象的指针,应该使用share_ptr指针,包括:
A、有一个指针数组,并且使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素。
B、两个对象包含都指向第三个对象的指针。
C、STL都包含指针,很多STL算法都支持赋值和复制操作,这些操作可用shared_ptr,不能使用unique_ptr(编译器给出警告)和auto_ptr(行为不确定)。
(2)、如果程序不需要多个指向同一个对象的指针时,我们优先使用unique_ptr。如果没提供,可选择使用Boost库里的scope_ptr,与unique_ptr类似。 如果函数使用new分配内存,并返回指向给内存的指针时,将其函数返回值声明为unique_ptr是个不错的选择。
Q:普通指针和智能指针的转换?
普通指针转换为智能指针时?它的构造函数有很多,其中一个就是以普通指针为参数的,通过这个构造函数,就能实现普通指针到智能指针的转换。
智能指针转换为普通指针?调用get函数即可。
int main()
{
//普通指针到智能指针的转换
int *ptr=new int(89);
shared_ptr<int> p1(ptr);
//智能指针到普通指针的转换
shared_ptr<int> p2(new int(38));
int *q=p2.get();
return 0;
}