在C++中为了更好的管理动态内存,C++98~C++11引入智能指针 auto_ptr,unique_otr,shared_ptr
与常规指针相比,智能指针在过期之后会自动销毁所分配的内存空间.
提到内存空间,有一些概念:
名称 | 存放方式及内容 |
---|---|
堆 | 由程序员分配和释放 |
栈 | 保存局部变量级函数参数等,系统分配和释放 |
常量区(全局区)) | 系统释放 |
代码区 | 存放函数等二进制代码 |
例如:
引用网上的解释 :
int a=0;//全局初始化区
char *p;//全局未初始化区
int main()
{
int b;//栈
char s[]="abc";//栈
char *p2;//栈
char *p3="123456";//p3在栈,"123456"在常量区
static int c=0;//全局初始化区
p1=(char*)malloc(10);//分配的内存在堆区
return 0;
}
再看代码
void remodel(std::string & str)
{
std::string *ps=new std::string(str);//这里new 分配出来的内存在堆上
...
str=ps;
return ;//函数执行到这里,指针ps保存的地址值被清掉,地址所指的堆内存却未处理,导致内存泄露
//应该在return之前加上 delete ps;语句
}
这段代码,我们自己new开辟的内存还要自己手动释放,但很多时候,忘了,导致内存泄露
引入智能指针,就是避免了我们忘记释放内存而导致内存泄露
智能指针其实是个类对象,只是行为类似指针
使用智能指针要包含头文件:
#include<memory>
- auto_ptr
- shared_ptr
- unique_ptr
//auto_ptr.cpp
#include<iostream>
#include<memory>
#include<string>
class Report
{
private:
std::string str;
public:
Report(const std::string s):str(s)
{std::cout<<"Object be created!";}
~Report(){std::cout<<"Object deleted!";}
void comment() const {std::cout<<str<<"\n";}
};
int main()
{
{
std::auto_ptr<Report>ps (new Report("using auto_ptr"));
ps->comment();
}//ps 有效期就在这个代码块里,出了该范围就过期,auto_ptr帮我们自动销毁ps所指内存
{
std::shared_ptr<Report>ps (new Report("using shared_ptr"));
ps->comment();
}//ps 有效期就在这个代码块里,出了该范围就过期,shared_ptr帮我们自动销毁ps所指内存
{
std::unique_ptr<Report>ps (new Report("using unique_ptr"));
ps->comment();
}//ps 有效期就在这个代码块里,出了该范围就过期,unique_ptr帮我们自动销毁ps所指内存
return 0;
}
输出结果:
Object created!
using auto_ptr
Object delete!
Object created!
using shared_ptr
Object delete!
Object created!
using unique_ptr
Object delete!
但c++11中 auto_ptr被摒弃,因为auto_ptr同类指针之间相互赋值,产生了空指针,使用空指针,会导致问题!(也有说,两个指针指向同一个对象,智能指针释放内存空间时,该空间被连续释放两次)
代码如下:
auto_ptr<string>ps (new string(" 哈哈哈哈"));
auto_ptr<string> ps1;
ps1=ps;//ps 失去新建对象的所有权,ps指的对象被ps1剥夺,ps变成空指针,ps1 指向new 出来的"哈哈哈哈"
std::cout<<ps<<"\n";
//再次使用到ps指针时会报异常,Sementation 11(g++ 编译得出))
给出C++ primer plus上的源代码
//fowl.cpp -- auto_ptr a poor choice
#include<iostream>
#include<string>
#include<memory>
int main()
{
using namespace std;
auto_ptr<string> films[5]=
{
auto_ptr<string>(new string("Fowl Balls")),
auto_ptr<string>(new string("Duck Walks")),
auto_ptr<string>(new string("Chicken Runs")),
auto_ptr<string>(new string("Turkey Errors")),
auto_ptr<string>(new string("Goose Eggs"))
};
auto_ptr<string> pwin;
pwin=films[2];//films[2]失去对"chicken runs" 所有权
cout<<"The nominees for best avian baseball film are \n";
for(int i=0;i<5;i++)
cout<<*films[i]<<endl;//当然了,当i=2 ,循环停止,因为会报异常:Sementation 11(具体因系统而异) ,因为films[2] 变成空指针了
cout<<"The winner is :"<<*pwin<<"!\n";
cin.get();
return 0;
}
运行报错:
将auto_ptr改为shared_ptr
运行ok:
因为使用shared_ptr 后pwin和films[2]指向的是同一个对象,引用计数变为2,pwin调用后销毁,计数变为1,films[2]数组再次调用销毁,计数变为0,空间真正被释放
那么换为uinque_ptr会怎样?
直接会在编译是报错,
unique_ptr比auto_ptr 更安全
unique_ptr 指的是一个对象,只能有一个智能指针拥有它(空间和时间上).该指针会在编译阶段判断,有异常不会在运行阶段爆发,所以说更安全
特别指出的是,如果有一个智能指针所指的对象,在该对象赋给另一个智能指针时,前一个智能指针已经被销毁,那么也是可以的,例如:
unique_ptr<string> demo(string &str)
{
unique_ptr<string> temp (new string(str));
return temp;
}
unique_ptr<string> ps;
ps=demo("UNIQUE_PTR TEST");
//demo() 返回一个临时unique_ptr ,然后ps剥夺temp 指针所指的对象所有权,随后temp被销毁,不复存在,没有机会使用它访问无效数据,这就是时间上允许两个指针都指向同一对象.
若是temp会和ps共存一段时间,那么是不被允许的
例如:
unique_ptr<string>temp(new string("Hi ho!"));
unique_ptr<string>ps;
ps=temp;//不被允许,temp(剥夺所有权后为空指针)会同时存在一段时间,有可能会被使用
//可以使用std::move()更改,ok
unique_ptr<string> ps1;
ps1=unique_ptr<string>(new string("hah")));//允许, 调用unique_ptr<string>构造函数创建一个临时变量,被剥夺所有权后被销毁
ps=temp;//不被允许,temp(剥夺所有权后为空指针)会同时存在一段时间,有可能会被使用但可以使用std::move()更改,ok ps=move(temp);
更改前面代码:
int main()
{
using namespace std;
unique_ptr<string> films[5]=
{
unique_ptr<string>(new string("Fowl Balls")),
unique_ptr<string>(new string("Duck Walks")),
unique_ptr<string>(new string("Chicken Runs")),
unique_ptr<string>(new string("Turkey Errors")),
unique_ptr<string>(new string("Goose Eggs"))
};
unique_ptr<string> pwin;
pwin=move(films[2]);//films[2]失去对"chicken runs" 所有权
cout<<"The nominees for best avian baseball film are \n";
for(int i=0;i<5;i++)
{
if(i==2)
continue;//当i=2 ,循环停止,因为会报异常:Sementation 11(具体因系统而异) ,因为films[2] 变成空指针,所以我用continue语句跳过i=2,从i=3处执行,这样就OK
cout<<*films[i]<<endl;
}
cout<<"The winner is :"<<*pwin<<"!\n";
cin.get();
return 0;
}
结果:
auto_ptr 智能指针不能自动销毁new 出来的数字
eg
int main()
{
using namespace std;
double *pd=new double;
*pd=1.222;
unique_ptr<double> spd(pd);//ok
double *pd1=new double[11]{9.0,9.0,9.0,9.0,9.0,9.0,9.0,9.0,4.0};
for(int i=0;i<9;i++)
cout<<"pd1["<<i<<"] = "<<pd1[i]<<endl;
// delete[] pd1;
return 0;
}
运行如下:
未delete[]pd1,堆内存未被回收
修改如下:
int main()
{
using namespace std;
double *pd=new double;
*pd=1.222;
unique_ptr<double> spd(pd);//ok
double *pd1=new double[11]{9.0,9.0,9.0,9.0,9.0,9.0,9.0,9.0,4.0};
{
unique_ptr<double[]> spd1(pd1);
}
for(int i=0;i<9;i++)
cout<<"pd1["<<i<<"] = "<<pd1[i]<<endl;
// delete[] pd1;
return 0;
}
运行:
可见,内存被销毁了!
最后说的是:
使用new 分配内存时,才能使用auto_ptr,shared_ptr,不是new分配的内存,不能使用,而使用new[],可以使用unique_ptr