-
内存安全
在C++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象来进行初始化;delete,接收一个动态对象的指针,销毁该对象,并释放与之关联的内存。
动态内存的使用很容易出问题,因为确保在正确的时间释放内存是及其困难的。有时我们会忘记释放内存(或程序抛出异常),在这种情况下就会产生内存泄漏;有时在尚有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针(段错误)。
下面写个Demo测试程序
#include#include #define FLAG 3 //用于编译不同的程序class Demo1{public: Demo1() { std::cout << "Demo1" << std::endl; } ~Demo1() { std::cout << "~Demo1" << std::endl; }};bool throw_test(bool flag){ if (flag) { throw "throw_test"; } return flag;}int main(int argc, char* argv[]){ Demo1 *pDemo1 = new Demo1(); #if (FLAG == 1) throw_test(true); //1.执行这条语句,会打印~Demo1? #endif #if (FLAG == 2) try { throw_test(true); } catch (...) //2....代表捕获所有异常 { delete pDemo1; //3.执行这条语句,会打印~Demo1? throw; } #endif #if (FLAG == 3) delete pDemo1; #endif return 0;}
上述程序结果如下:
当宏FLAG为1时,执行throw_test(true),即使程序抛出异常,它没有打印~Demo1;
当宏FLAG为2时,执行throw_test(true),程序会抛出异常,之后捕获到异常打印~Demo1;
当宏FLAG为3时,执行delete pDemo1,这是正常操作,程序会调用Demo1的析构函数,打印~Demo1;
-
智能指针
C++中的智能指针类型有:auto_ptr,shared_ptr,unique_ptr,weak_ptr(后三者为C++11新增的),它们均为类模板,使用需要包含<memory>头文件。
常规指针带来的风险
Demo1* pDemo1 = new Demo1();Demo1* pDemo2;pDemo2 = pDemo1;printf("pDemo1:%x pDemo2:%x\n", pDemo1, pDemo2);
上面的pDemo1和pDemo2是常规指针,指向同一个对象(浅拷贝),因此打印的地址也是一样的;请试想一下如果其中一个指针执行了delete操作,那么另一个指针再执行别的操作会怎样?程序会发生段错误。要避免这个问题,可以用下面这些方案:
- 定义赋值运算符函数,进行深拷贝,这样的操作会使上面的两个指针不再指向同一个对象,缺点是浪费空间,所以智能指针都未采用此方案
- "独占"所指的对象;对于特定的对象,某一时刻只能有一个指针指定一个给定对象,当指针被销毁时,它所指的对象也被销毁,这就是用于auto_ptr和uniqiie_ptr 的策略,但unique_ptr的策略更严格。
- 利用引用计数,创建记录型的指针;例如,赋值时,计数将加1,而指针过期时,计数将减1。当减为0时才调用delete。这是shared_ptr采用的策略。
在C++11中,auto_ptr已弃用;编写一段测试程序,Demo1类还是使用上面定义的。
int main(int argc, char *argv[]){ std::auto_ptrpDemo1(new Demo1); std::auto_ptr pDemo2; pDemo2 = pDemo1; printf("pDemo1:%p pDemo2:%p\n", pDemo1, pDemo2); //运行到这里pDemo1会打印什么? return 0;}
上面的程序运行后,打印pDemo1的地址为NULL。具体可以查看下auto_ptr的赋值运算符的实现是如何的。
下面这两张截图是VS2015下的auto_ptr的赋值运算符的实现;我们可以看到使用赋值运算符时,会调用reset函数,这是会将_Myptr的内存delete,并将地址置为NULL;如果pDemo1调用成员函数,此时会发送什么?
auto_ptr存在内存崩溃的风险,这个或许就是auto_ptr被C++11弃用的原因吧。
未完待续...