在上面这个程序中,我们在第8行就将指针p利用delete删掉了。但是,我们来看看程序的输出结果:
对照着上面的程序,我们来分析一下这个输出。首先,我们在程序的第5行初始化了一个指针p。之后输出指针p读取的值。由于第6行的原因,程序肯定会输出3 了。之后,我们在程序的第8行删除了这个指针p。但是我们惊奇的发现,在程序的第9行竟然可以输出指针p读取的值。我们不是已经把它删了么?其实不 然,debug,上图:
从监视窗口中,我们可以看见虽然程序的第8行已经将指针p删除了,但是在监视窗口中p仍然存在,只是*p所指向的值不再是原来的3了,而是一个随机数。这里就说明了一个非常重要的概念:
我们在删除一个指针之后,编译器只会释放该指针所指向的内存空间,而不会删除这个指针本身。
然后我们接着往下分析。在程序的第10行我们又创建了一个long型的指针p1。在12行与13行的输出中我们惊奇地发现,指针p保存的地址居然和指针 p1保存的地址一模一样!这个就说明了指针p和指针p1都指向内存的同一个地方!!!出现这种状况的原因其实是由于编译器。
编译器默认将释放掉的内存空间回收然后分配给新开辟的空间。
所 以在第11行由于我们新开辟了一个可以保存long型变量的空间并且由p1来指向它,那么这里的p1指向的其实就是在程序第8行释放掉的内存空间,即p指 向的内存空间!所以,这就导致了两个指针同时指向同一个内存空间。这是多不安全的一件事情啊!要知道,我们是把指针p删了的啊!如果再重新对*p进行赋值 操作,那么不是会连着*p1一起改动么?
果然,让我们担心的事情出现了。我们明明在程序的第11行中定义了*p1的值为100,但是在输出上面,指针p1读取的值竟然也是23。这个原因就是因为 野指针p造成的。我们可以看到,在程序的第14行我们将23赋给了*p。又由于p和p1指向的是同一块内存单元,所以在这里相当于也将p1所指向的内存单 元中的值(原来是100),改成了23!这样必然会导致程序的出错!
那么我们就不禁要问了,对于这种由于野指针造成的问题,有没有解决的方法呢?答案当然是有的了。我们只需要牢记下面这句话:
在删除一个指针之后,一定将该指针设置成空指针(即在delete *p之后一定要加上: p=NULL)
我们来看一下在stdio.h中关于关键字NULL的定义:
本文转载自:众所周知,最开始我们用new来创建一个指针,那么等我们用完它之后,一定要用delete将该指针删掉。但是,值得注意的是,难道就仅仅是删除这个指针这么简单的么?下面,我们用一个程序来说明这个问题: #include02using namespace std;
Qt智能
指针
--QScopedPointer ⽂章⽬录 概述 前⼀篇⽂章我们详细的介绍了的⽤法,那么,这⾥继续总结Qt的另⼀个智能
指针
QScopedPointer的⽤法。 QScopedPointer和
C++
中的智能
指针
std::unique_ptr其概念是⼀样的,它包装了new操作符在堆上分配的动态对象,能够保证动态创建 的对象在任何时候都可以被正确地
删除
。但它有更严格的所有权,并且不能转让,⼀旦获取了对象的管理权,你就⽆法再从它那⾥取回来。 也就是说,只要出了作⽤域,
指针
就会被⾃动
删除
,因为它的拷贝构造和赋值操作都是私有的,与QObject及其派⽣类风格相同。 QScopedPointer ⾸先我们来看⼀个官⽅⽰例: 没有使⽤智能
指针
: void myFunction(bool useSubClass) { MyClass *p = useSubClass ? new MyClass() : new MySubClass; QIODevice *device = handsOverOwnership(); if (m_value > 3) {
delete
p;
delete
device; return; } try { process(device); } catch (...) {
delete
p;
delete
device; throw; }
delete
p;
delete
device; } 上⾯的写法,稍有不慎就会导致内存泄露,但是如果使⽤智能
指针
,就会变得很简单了: void myFunction(bool useSubClass) { QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass); QScopedPointer<QIODevice> device(handsOverOwnership()); if (m_value > 3) return; process(device); } 注意:因为拷贝构造和赋值操作私有的,所以不能⽤作容器的元素。 const 限制 C ++
指针
的const限定也可以⽤QScopedPointer表⽰: const QWidget *const p = new QWidget(); // 等同于: const QScopedPointer<const QWidget> p(new QWidget()); QWidget *const p = new QWidget(); // 等同于: const QScopedPointer<QWidget> p(new QWidget()); const QWidget *p = new QWidget(); // 等同于: QScopedPointer<const QWidget> p(new QWidget()); 考虑⼀种情况 上⾯说到,使⽤QScopedPointer智能
指针
动态创建的对象,⼀旦出了作⽤域就会 被⾃动释放并置空,那么如果
需
要函数返回值怎么办 呢? ⽐如下⾯这种情况: QLabel * createLabel() { QScopedPointer<QLabel> pLabel(new QLabel()); // return pLabel.data(); //invalid return pLabel.take(); //valid } int main(int argc, char *argv[]) { QApplication a(argc, argv); QScopedPointer<QLabel> p1(createLabel()); p1->setText("hello"); p1->show(); return a.exec(); } 注意,我们在createLabel()函数中创建label对象并返回时,不能使⽤data(),⽽要使⽤take(); 因为 T *QScopedPointer::data() const返回指向对象的常量
指针
,QScopedPointer仍拥有对象所有权。 所以通过data()返回过后就被 ⾃动
删除
了,从⽽导致mian函数中的p1变成了
野指针
,程序崩溃。 ⽽使⽤T *QScopedPointer::take()也是返回对象
指针
,但QScopedPointer不再拥有对象所有权,⽽是转移到调⽤这个函数的caller,同 时QScopePointer对象
指针
置为NULL。 另外还有⼀个函数要注意。 void QScopedPointer::reset(T *other = Q_NULLPTR):
delete
⽬前指向的对象,调⽤其析构函数,将
指针
指向另⼀个对象other,所有 权转移到
Qt源码剖析-智能
指针
⽬录 智能
指针
是什么? 智能
指针
是什么? 1. 智能
指针
是使⽤RAII技术(惯⽤⼿法)对裸
指针
进⾏封装、隔离、管理。 2. 把堆对象转移为栈对象。 为什么使⽤ 为什么使⽤QT智能 智能(smart)
指针
?
指针
? CPP中在使⽤堆内存时我们必须使⽤
指针
,使⽤
指针
给我带来便利和强⼤的威⼒同时也给我们带来了很多隐患,所以
指针
也是把双刃剑。 常见的隐患有: 1. 我们在使⽤new创建⼀个对象的同时,由于种种原因⽽忘记了
delete
从⽽导致内存泄漏影响应⽤运⾏效率 2. 我们在使⽤⼀个
指针
的时候不知道该
指针
指向的对象是否已经析构,从⽽导致使⽤⼀个错误的
野指针
。 针对上述问题Qt提供⼀组类模板(Smart
指针
类)来管理
C++
中的裸
指针
, 同时提供相关重载运算符使得使⽤起来与
指针
操作⽆异。
指针
的⽣ 命周期也交由Smart类来管理⽽不⽤程序员⾃⼰去⼿动管理。使⽤Smart
指针
进⽽避免了上述隐患的存在。 Qt提供了哪些智能
指针
? 提供了哪些智能
指针
? QPointer、QSharedPointer、QScopedPointer、QWeakPointer、QSharedDataPointer、QExplicitlySharedDataPointer、 QGuard、QtPatternist::AutoPtr 由于Qt智能
指针
较多,避免篇幅较长本⽂只对平时使⽤场景较多的QSharedPointer源码进⾏分析也⽐较有代表性 QSharedPointer 官⽅介绍: QSharedPointer类持有对共享
指针
的强引⽤。QSharedPointer是
c++
中的⼀个⾃动共享
指针
。它的⾏为与⽤于普通⽬的的普通
指针
完全 相同,包括对constness的尊重。当
指针
超出作⽤域时,QSharedPointer将
删除
它所持有的
指针
,前提是没有其他QSharedPointer对象 引⽤它。QSharedPointer对象可以从普通
指针
、另⼀个QSharedPointer对象或通过将QWeakPointer对象提升为强引⽤来创建。 本⽂基于Qt5.12.4版本分析 QSharedPointer类模板 类模板(smart
指针
类
指针
类)源码分析 源码分析 QSharedPointer类源码篇幅较长,避免出现阅读疲劳以下分段解读。 1. QSharedPointer类模板对于模板参数 类模板对于模板参数T的类型重定义 的类型重定义 template <class T> class QSharedPointer { typedef T *QSharedPointer:: *RestrictedBool; typedef QtSharedPointer::ExternalRefCountData Data; public: typedef T Type; typedef T element_type; typedef T value_type; typedef value_type *pointer; typedef const value_type *const_pointer; typedef value_type &reference; typedef const value_type &const_reference; typedef qptrdiff difference_type; }; QSharedPointer提供了部分公有和私有的对于模板参数T的类型重定义(说明: 这⾥T类型并不是指⼀个特定类型,⽽是⼀个通⽤/万能类 型。 我们可以把类模板看做成⼀个函数 QSharedPointer为函数名, template中类型列表的为函数形参列表,typedef 为返回值。 T最终 的类型确认待你使⽤时由编译器实例化模板时才能推导出),QSharedPointer类模板分别重定义了模板⼊参T的类型别名、
指针
、常
指针
、 引⽤、常引⽤类型。这也是模板的惯⽤⼿法和伎俩⽅便对于类型的获取和使⽤ 原因有以下⼏点: a. 提供公有的对于T的类型重定义⽅便的类外部获取和使⽤T及其衍⽣类型。 b. 类内部实现过程中统⼀,规范对于T的使⽤。 2. QSharedPointer的成员属性 的成员属性 template <class T> class QSharedPointer { // @1 Type *value; // @2 Data *d; }; QSharedPointer成员属性⽐较简洁代码段@1声明成员属性value(
需
要管理的裸
指针
), 代码段@2声明属性d。Data类型我们从第1点中可 知为QtSharedPointer::ExternalRefCountData 的类型别名。主要作⽤为记录当前持有value的QSharedPointer
以上代码,因为指向的内存已经被释放,导致后面的调用失效,这种情况导致程序崩溃的原因会非常难找,而且出错的位置很难定位,开发时往往还容易遇到很多这种情况。
那么
delete
到底做了什么动作呢?
delete
指针
只是释放了那个
指针
原本所指的内存而已,并没
在本地调试树形结构时,常常
需
要释放一棵树里全部
指针
所占的空间,这一步不
需
要了解树的具体结构;
我认为最简单通用的方法就是用广度优先周游的方法将树中的非空节点依次
删除
;
但这里由于
指针
操作的复杂性,写出来的代码常常出现以下两种情况:1.传递的是
指针
的值而非
指针
本身,从而没有释放想释放的空间 2.
删除
过程中出现了runtime error,即访问了不能访问的空间(如悬垂
指针
or 系统自留地址)
学习
C++
新手通常会对
指针
的
使用
比较头疼,其实
指针
的概念很简单,只要能悟清楚这样一个简单的道理就能对
指针
有一定的认识了: 例如 int *a = new int[10]; 一维
指针
其实就相当于一维数组,不用去看书上所说的数组在内存中的首地址这些晦涩的话,以此类推 二维
指针
就相当于二维数组,新手对一维数组的开辟与释放比较容易熟悉,例如上面的a 释放就是
delete
[]a; a = NULL; 注意a = NULL; 必须要加上,这样是为了避免这个
指针
会变成“
野指针
”。写程序时一定要注意规范性和严谨性,避免可能会出现的错误。 代码如下://二维
指针
的开辟与释放int **b = NULL;b
2.2 代码行... 16
2.3 代码行内的空格... 17
file:///D|/My Documents/学习/高质量
C++
-C编程指南.txt[2008-11-11 21:48:26]
2.4 对齐... 18
2.5 长行拆分... 19
2.6 修饰符的位置... 19
2.7 注释... 20
2.8 类的版式... 21
第3章 命名规则... 22
3.1 共性规则... 22
3.2 简单的Windows应用程序命名规则... 23
3.3 简单的Unix应用程序命名规则... 25
第4章 表达式和基本语句... 26
4.1 运算符的优先级... 26
4.2 复合表达式... 27
4.3 if 语句... 27
4.4 循环语句的效率... 29
4.5 for 语句的循环控制变量... 30
4.6 switch语句... 30
4.7 goto语句... 31
第5章 常量... 33
5.1 为什么
需
要常量... 33
5.2 const 与 #define的比较... 33
5.3 常量定义规则... 33
5.4 类中的常量... 34
第6章 函数设计... 36
6.1 参数的规则... 36
6.2 返回值的规则... 37
6.3 函数内部实现的规则... 39
6.4 其它建议... 40
6.5
使用
断言... 41
6.6 引用与
指针
的比较... 42
file:///D|/My Documents/学习/高质量
C++
-C编程指南.txt[2008-11-11 21:48:26]
第7章 内存管理... 44
7.1内存分配方式... 44
7.2常见的内存错误及其对策... 44
7.3
指针
与数组的对比... 45
7.4
指针
参数是如何传递内存的?... 47
7.5 free和
delete
把
指针
怎么啦?... 50
7.6 动态内存会被自动释放吗?... 50
7.7 杜绝“
野指针
”... 51
7.8 有了malloc/free为什么还要new/
delete
?... 52
7.9 内存耗尽怎么办?... 53
7.10 malloc/free 的
使用
要点... 54
7.11 new/
delete
的
使用
要点... 55
7.12 一些心得体会... 56
第8章
C++
函数的高级特性... 57
8.1 函数重载的概念... 57
8.2 成员函数的重载、覆盖与隐藏... 60
8.3 参数的缺省值... 63
8.4 运算符重载... 64
8.5 函数内联... 65
8.6 一些心得体会... 68
第9章 类的构造函数、析构函数与赋值函数... 69
9.1 构造函数与析构函数的起源... 69
9.2 构造函数的初始化表... 70
9.3 构造和析构的次序... 72
9.4 示例:类String的构造函数与析构函数... 72
9.5 不要轻视拷贝构造函数与赋值函数... 73
9.6 示例:类String的拷贝构造函数与赋值函数... 73
9.7 偷懒的办法处理拷贝构造函数与赋值函数... 75
9.8 如何在派生类中实现类的基本函数... 75
file:///D|/My Documents/学习/高质量
C++
-C编程指南.txt[2008-11-11 21:48:26]
9.9 一些心得体会... 77
第10章 类的继承与组合... 78
10.1 继承... 78
10.2 组合... 80
第11章 其它编程经验... 82
11.1
使用
const提高函数的健壮性... 82
11.2 提高程序的效率... 84
11.3 一些有益的建议... 85
参考文献... 87
附录A :
C++
/C代码审查表... 88
附录B :
C++
/C试题... 93
附录C :
C++
/C试题的答案与评分标准... 97
C++
浅拷贝与深拷贝及引用计数分析
在
C++
开发中,经常遇到的一个问题就是与
指针
相关的内存管理问题,稍有不慎,就会造成内存泄露、内存破坏等严重的问题。不像Java一样,没有
指针
这个概念,所以也就不必担心与
指针
相关的一系列问题,但
C++
不同,从C语言沿袭下来的
指针
是其一大特点,我们常常要
使用
new/
delete
来动态管理内存,那么问题来了,特别是伴随着
C++
的继承机制,如
野指针
、无效
指针
使用
、内存泄露、double free、堆碎片等等,这些问题就像地雷一样,一不
小心
就会踩那么几颗。
先来谈一下
C++
类中常见的浅拷贝问题,以及由此引发的double free。什么是浅拷贝?当类中的成员变量包括指
C++
标准规定:
delete
空
指针
是合法的,没有副作用。所以我们在
Delete
指针
后赋值为NULL或0是个好习惯。对一个非空
指针
delete
后,若没有赋NULL,若再次
delete
的话有可能出现问题。
int *ptr = new int(3);
delete
ptr;
ptr = NULL;
可以看出
delete
是释放
指针
指向的内存,并不是
指针
本身所占有的内存。所以
delete
后,
指针
的还是指向那块区域,并未清0,所以如果下次用到,就会发生xxx空间不能访问的异常。以后
delete
后要赋值为空。