赞
踩
C++ 智能指针
智能指针行为类似于指针的类对象,可帮助管理动态内存。通俗的讲,智能指针的对象是一种指针,可以将new获得的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。
参考:C++ Primer Plus P100-107
指针其实也是一个变量,存储的是值的地址,*
运算符被称为间接值或解除引用运算符。例如有指针变量pointer
,则pointer
是一个地址,*pointer
表示存储在该地址的值。(&
运算符表示取地址的值或者引用)
int a = 5;
int *pointer = &a;
这句话的意思是将a的地址的值存放在pointer
变量中,可以通过*pointer
来对变量进行操作。
int *pt;
pt = (int*)0xB80000000
其中,0xB80000000
是一段偏移地址,要把这个数字作为地址来使用,需要对这其进行强制类型转换,利用(int*)
转换成适当的地址类型。
在声明指针时,会为指针变量分配内存地址,但是不会为指针指向的数据分配内存,所以如果只是声明指针却不初始化指针,就直接利用指针来进行赋值操作,就会发生错误,因为指针不知道具体指向哪,也就是所说的野指针,这样是非常危险的。
int *pt;
*pt = 8;
指针不进行初始化,那么它可以是任何值,所以修改的pt
指向那块地址的值很可能不是要存储我们赋给它的值,它甚至可以修改程序的任意位置的值。
在C语言中,可以使用malloc()
函数来分配内存,在C++中,也可以使用malloc()
函数来分配,但它有更好的方法----new运算符。
int *pt = new int;
new
会根据类型来确定一定字节的内存,然后在堆(heap)中分配,将这个内存块的地址返回给pt。new
分配的内存块和常规变量声明分配的内存块不同,常规变量被存储在栈(stack)中,new从堆(heap)和自由存储区(free store)中分配。(如果无法分配,则会返回0,空指针(nullptr
))。
当new针对类的对象分配时,这时候会执行构造函数。
当我们不需要内存时,我们可以使用delete
运算符释放内存,delete
后接指针:
int *pt = new int;
...
delete pt;
释放pt
指向的内存,但是不会删除指针本身,可以利用new
重新分配内存。另外,当内存已经释放时,不能再次释放。
delete pt;
delete pt;//不能再次释放
再者,delete
和new
是配套使用:
int a=5;
int *pn = &a;
delete pn;//非法语句,指向的内存不是用new分配在堆上的
所以,只能用delete
来释放new
分配的内存。
对于面向的类对象而言,在new
分配内存的时候,就会执行构造函数;在delete
释放内存时,会调用析构函数来释放内存。
声明创建数组会在程序编译时创建内存,不管使不使用,都会占用内存,属于静态联编;如果使用new
,在运行阶段创建数组,在程序运行时选择数组长度,属于动态联编。
int *pt = new int[10];//创建动态数组
delete [] pt;//释放内存
两者是配套使用的,如果只使用了对于普通类型的指针仅仅使用delete pt
,而不使用delete [] pt
,虽然程序不会报错,但不建议这样使用;对于数组对象而言,这样的做法就是错误的,一定要配套使用delete []
;
使用原则:
不要用delete释放不是new分配的内存;
不要用delete释放内存两次;
如果使用new []分配内存,应使用delete []释放内存;
空指针对于delete是安全的;
当一个内存被new
创建时,若没有使用delete
来释放内存,则会导致内存泄露,C++的指针是非常高效的工具同时也是非常危险的;为了避免内存泄露必须配套的使用new
和delete
;为此,C++的智能指针能够更高效的管理指针,可以不使用delete
来释放内存,更加安全高效。(auto_ptr
,unique_ptr
,shared_ptr
)其中,auto_ptr
已经不再使用。
参考:C++ Primer Plus P667-P673 (智能指针)
使用智能指针,需要包含头文件 < memory > ,创建智能指针对象:
std::shared_ptr<double> pt(new double);
所有的智能指针都有一个explicit
构造函数,可以把指针作为参数,因此不需要自动将指针转换为智能对象:
//分别创建一个智能指针对象和普通指针
std::shared_ptr<double> pd;
double *pn = new double;
//指针操作
pd = pn; //非法,这是一种隐式转换,而构造函数是声明为explicit的
std::shared_ptr<double> pshared = pn;//也是非法,理由同上
pd = std::shared_ptr<double>(pn);//合法
std::shared_ptr<double> pshared(pn);//合法
shared_ptr
的策略:
采用引用计数,当赋值时,计数加1,指针过期时,计数减1,当最后一个指针过期时,才调用delete
,同样也适合拷贝构造函数。
该智能指针保证了内存块的指针唯一性,若将unique_ptr
的指针赋值给其他指针时,在编译的过程中便会报错。(编译报错相比于程序运行崩溃更加安全,更加容易发现)
std::unique_ptr<double> pn(new double);
std::unique_ptr<double> pt;
pt = pn;
//编译器会报错
但对于auto_ptr
而言,这样的语句是合法的。
std::auto_ptr<double> pn(new double);
std::auto_ptr<double> pt;
pt = pn;
pn
的控制权会交由pt
,pn
的所有权被剥夺,但如果程序后依然使用pn
,它不能指向有效数据,则会导致程序崩溃;同时这样的操作会导致出现悬挂指针pn
。相比于auto_ptr
而言,unique_ptr
更加安全。
auto_ptr
和unique_ptr
都只能用new
创建,不能使用new []
,但对于shared_ptr
,则可以使用new []
和delete []
make_shared
会返回一个shared_ptr
的对象,它不需要使用new
关键字,只分配一次内存,效率较高,减少了内存分配的开销。
所以尽量可以使用make_shared
来创建。
但其缺点是,当类构造函数是保护成员或私有成员时,不能使用make_shared
.
std::shared_ptr<string> pt;
pt = std::make_shared<string>("hello");
可以对指针数组的创建:
std::unique_ptr<Eigen::Vector2d[]> points(new Eigen::Vector2d[100]);
或者利用boost库:
boost::shared_array<Eigen::Vector2d> points(new Eigen::Vector2d[100]);
利用shared_ptr:(在C++17以后才支持)
std::shared_ptr<int[]> p(new int[100]);
如果编译器不支持,可以采用下列方式:
std::shared_ptr<int> p(new int[100], [](int* p) {delete[] p; });
std::shared_ptr<int> p(new int[100], std::default_delete<int[]>());
具体参考:
https://www.jianshu.com/p/03eea8262c11
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。