智能指针
当我们使用指针在堆上分配空间时,大部分人的做法都是指针完成任务的时候使用delete操作符进行释放。但是总有意外产生。
void remodel(std::string str)
{
std::string * ps = new std::string(str);
...
if(weird_thing())
{
throw exception();
}
str = *ps;
delete ps;
return ;
}
当上述代码出现异常时,delete将不被执行,因此也会导致内存泄漏。
而智能指针是很解决这个问题的一种有效手段。
智能指针在到期时可以自动释放堆区上的内存,但是值得注意的是智能指针不能用于非堆内存。
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class Report
{
private:
string str;
public:
Report(const string s) : str(s)
{
cout << "Object create!" << endl;
}
~Report()
{
cout << "Object destory!" << endl;
}
void comment() const
{
cout<< str <<endl;
}
};
int main()
{
//虽然都在堆区开辟了空间,但是都调用了析构函数
{
auto_ptr<Report> ps(new Report("using auto_ptr"));
ps->comment();
}
{
shared_ptr<Report> ps(new Report("using auto_ptr"));
ps->comment();
}
{
unique_ptr<Report> ps(new Report("using auto_ptr"));
ps->comment();
}
//值得说明的一点是,智能指针不能应用于非堆内存
system("pause");
return 0;
}
三种智能指针都在到期的时候调用了析构函数,释放了堆区的内存
注意事项:
如果有两个以上的智能指针指向同一块内存,那么这两个指针到期的时候这一块内存会被释放两次,这是不能够接受的。要避免这种问题,可以使用如下的解决方法:
1.定义赋值运算符,避免浅拷贝。
2.建立所有权概念,对于特定的对象只有一个智能指针可以拥有它。
3.创建智能更高的指针,跟踪饮用特定对象的智能指针数。这成为称为引用计数。这是shared_ptr采用的策略。
shared_ptr和auto_ptr
事实上,再这样使用auto_ptr指针时,程序也会发生崩溃
#include<iostream>
#include<memory>
#include<string>
using namespace std;
int main()
{
auto_ptr<string> film[5] =
{
auto_ptr<string> (new string("aaaaa")),
auto_ptr<string>(new string("bbbbb")),
auto_ptr<string>(new string("ccccc")),
auto_ptr<string>(new string("ddddd")),
auto_ptr<string>(new string("fffff"))
};
auto_ptr<string> pwin;
pwin = film[2];
for(int i = 0; i < 5; ++i) cout << *film[i] << endl;
cout << *pwin << endl;
system("pause");
return 0;
}
上述这段代码,通过赋值操作,film[2]所指向的内存区域被转交给pwin,这导致film[2]对象不在引用该字符串。这是因为auto_ptr采用的是所有权模型,在同一时刻,只能允许一个对象只能有一个智能指针可以指向。
解决上述代码只需要将其中的auto_ptr换成shared_ptr即可。这是因为在shared_ptr内部采用的是引用计数,使得多个shared_ptr指针可以指向同一个对象
unique_ptr和auto_ptr
假设有如下的语句
auto_ptr<string> a(new string("aaa"));
auto_ptr<string> b;
b = a;
对于auto_ptr来说,在赋值之后,a会指向一块无效内存,编译器却不自知!
如果使用unique_ptr
unique_ptr<string> a(new string("aaa"));
unique_ptr<string> b;
b = a;
则编译器第三条语句就会报错,避免了a指向无效数据的问题。因此,unique_ptr比auto_ptr更加安全。在编译起就能发现的错误,比运行时在产生的错误好太多了。
但是并不是所有情况下,上述赋值都会出错,尤其在使用unique_ptr的时候,考虑如下代码:
unique_ptr<string> demo(char * s)
{
unique_ptr temp (new string(s));
return temp;
}
...
{
...
unique_ptr<string> a ;
a = demo("test");
...
}
上个时候,demo返回了一个临时变量unique_ptr,在a接管了地址之后,temp马上被销毁。在这种情况下,编译器允许这种赋值!
也就是说,如果赋值的对象时unique_ptr类型的临时右值,那么这种操作会被编译器允许,实际上在运行时也不会产生错误。
如果想执行赋值操作,可以使用std::move()函数
另外,unique_ptr还能和new[]一起使用,这是auto_ptr和shared_ptr都做不到的事情。
std::unique_ptr<double[]> pds(new double(5));