unique_ptr解决内存泄漏的问题。
C++中的智能指针并不是什么新技术。我们可以来看一下示例来理解一下智能指针如何更好地避免内存泄漏。
这是一个内存泄漏的例子,challenge方法中创建了一个Person对象,指针变量p指向该Person对象。并且将在指针p传给了gaming方法。那么头痛的问题来了,到底指针p指向的堆内存是在gaming函数还是challenge函数执行结束后内存释放呢?
#include <iostream>
#include <string>
class Person{
private:
int d_age;
std::string d_name;
public:
Person(int age,std::string name):d_age(age),d_name(name){
std::cout<<"生成Person对象"<<std::endl;
}
void ready(){
std::cout<<this->d_name<<"准备中..."<<std::endl;
}
void play(){
std::cout<<this->d_name<<"正在游戏..."<<std::endl;
}
};
void gaming(Person* p){
p->play();
}
void challenge(){
auto p=new Person(23,"艾可");
p->ready();
gaming(p);
}// 超出该作用域就发生内存泄漏
int main(){
challenge();
return 0;
}
如果采用手动delete的话,按照前文所示,new操作和delete操作位于同一函数上下文!但对于一段非常长而复杂的代码来说,这种做法仍然不可取的。因为跟踪一个指针什么时候不再被使用,那是一个繁重的工作,如果你使用C语言来开发一些底层的算法应用时,那么你必须考虑这个问题。
这个时候,我们可以使用C++11开始引入的一个叫unique_ptr(独占指针),上面的示例代码,我们可以修改为如下代码所示
#include <iostream>
#include <string>
#include <memory>
class Person{
private:
int d_age;
std::string d_name;
public:
Person(int age,std::string name):d_age(age),d_name(name){
std::cout<<"生成Person对象"<<std::endl;
}
~Person(){
std::cout<<"销毁Person对象"<<std::endl;
}
void ready(){
std::cout<<this->d_name<<"准备中..."<<std::endl;
}
void play(){
std::cout<<this->d_name<<"正在游戏..."<<std::endl;
}
};
void gaming(std::unique_ptr<Person> e){
e->play();
}//gaming函数独占指针e,在gaming执行完成后释放Person对象。
void challenge(){
auto p=std::make_unique<Person>(23,"艾可");
p->ready();
gaming(std::move(p));
}
int main(){
challenge();
return 0;
}
程序输出
执行challenge函数
生成Person对象
艾可准备中...
执行gaming函数
艾可正在游戏...
退出gaming函数
销毁Person对象
退出challenge函数
示例代码通过std::make_unique<T>模板函数并传入模板参数Person创建了一个Person对象,我们说此时的challege函数对Person内存实体的资源独占(Single OwnerShip)。然而Person对象的所有权可以通过std::move操作符从challenge函数让度给gaming函数。
因此gaming(std::move(p));这条代码意味着gaminig函数在执行时对Person对象单独占用。对于unique_ptr类型的指针的内存释放规则,谁独占对象,谁负责内存释放。
这里补充一些通过unique_ptr<T>类模板初始化一个对象时的语法问题。
//不能new返回的裸指针赋值给unique_ptr类型的指针。
std::unique_ptr<Person> p1=new Person();
//unique_ptr<T>本身就是一个类,unique_ptr<T> p1()
//表示一个带参数的构造函数,而new Person()
//刚好是该构造函数的参数
std::unique_ptr<Person> p1(new Person());
//unique_ptr的工厂方法
std::unique_ptr<Person> p1=std::make_unique<Person>();
//推荐这种方法
auto p1=std::make_unique<Person>();
//这是一种错误的做法,因为独占指针只能被某个类方法或函数独占,无法赋值
std::unique_ptr<Person> p2=p1;
//独占指针可以通过move操作符转移其所有权
std::unique_ptr<Person> p2=std::move(p1);
gaming(std::move(p1));
unique_ptr的生命周期
我们来看一下简化后的示例,在一个自定义的作用域内,通过std::unique_ptr创建了一个Person对象,在离开自定义的作用域,该对象会被自动销毁。因此不需要手动delete该对象指针。
int main(){
puts("执行main函数...");
{
puts("在定义的作用域当中");
auto p=std::make_unique<Person>(23,"Lisa");
puts("离开定义的作用域");
}
puts("退出main函数");
return 0;
}
程序输出
![716ffe89941becb01706f8da6a7341cb.png](https://i-blog.csdnimg.cn/blog_migrate/3619765cefade6c2e347d4cb46e16be2.png)
程序示例2:这段代码其实就是上文的一个简化版
void bar(std::unique_ptr<Person> p){
puts("进入bar函数...");
puts("离开bar函数...");
}
int main(){
puts("执行main函数...");
auto p=std::make_unique<Person>(23,"Lisa");
//过度所有权
bar(std::move(p));
puts("退出main函数");
return 0;
}
在main中函数栈中创建了一个unique_ptr<Person>指针,并且将该指针的所有权让度给bar函数。在bar函数执行完之时,bar函数负责对unique_ptr<Person>指针执行内存释放,main函数栈不需关心unique_ptr<Person>指针的内存释放问题。
程序输出
![7efaaca0f3f7fa752cab752d8cebf1f4.png](https://i-blog.csdnimg.cn/blog_migrate/2087b3dd7c46c7fe610a444951cb4975.png)
小结:
对于unique_ptr类型的指针的内存释放规则,谁独占对象,谁负责内存释放,并且unique_ptr指针的声明周期仅存活在独占它的函数上下文内。