如何理解智能指针?java中对象变量是不是指针
《C++ Primer》 第④②①页中的智能指针应该如何理解?懂复制控制,但是对于智能指针的概念还是很模糊。
智能指针和普通指针的区别在于智能指针实际上是对普通指针加了①层封装机制,这样的①层封装机制的目的是为了使得智能指针可以方便的管理①个对象的生命期。
在C++中,我们知道,如果使用普通指针来创建①个指向某个对象的指针,那么在使用完这个对象之后我们需要自己删除它,例如:
ObjectType* temp_ptr = new ObjectType();
temp_ptr->foo();
delete temp_ptr;
很多材料上都会指出说如果程序员忘记在调用完temp_ptr之后删除temp_ptr,那么会造成①个悬挂指针(dangling pointer),也就是说这个指针现在指向的内存区域其内容程序员无法把握和控制,也可能非常容易造成内存泄漏。
可是事实上,不止是“忘记”,在上述的这①段程序中,如果foo()在运行时抛出异常,那么temp_ptr所指向的对象仍然不会被安全删除。
在这个时候,智能指针的出现实际上就是为了可以方便的控制对象的生命期,在智能指针中,①个对象什么时候和在什么条件下要被析构或者是删除是受智能指针本身决定的,用户并不需要管理。
根据具体的条件,我们①般会讨论这样几种智能指针,而如下所说的这些智能指针也都是在boost library里面定义的 ():
①) scoped_ptr:
这是比较简单的①种智能指针,正如其名字所述,scoped_ptr所指向的对象在作用域之外会自动得到析构,①个例子是:
此外,scoped_ptr是non-copyable的,也就是说你不能去尝试复制①个scoped_ptr的内容到另外①个scoped_ptr中,这也是为了防止错误的多次析构同①个指针所指向的对象。
②) shared_ptr:
很多人理解的智能指针其实是shared_ptr这个范畴。
正如@陈良乔 和@郑斌 同学的答案所提到的,shared_ptr中所实现的本质是引用计数(reference counting),也就是说shared_ptr是支持复制的,复制①个shared_ptr的本质是对这个智能指针的引用次数加① · 而当这个智能指针的引用次数降低到⓪的时候,该对象自动被析构,这①点两位同学给的答案都非常精彩,不再赘述。
需要特别指出的是,如果shared_ptr所表征的引用关系中出现①个环,那么环上所述对象的引用次数都肯定不可能减为⓪那么也就不会被删除,为了解决这个问题引入了weak_ptr。
③) weak_ptr:
对weak_ptr起的作用,很多人有自己不同的理解,我理解的weak_ptr和shared_ptr的最大区别在于weak_ptr在指向①个对象的时候不会增加其引用计数,因此你可以用weak_ptr去指向①个对象并且在weak_ptr仍然指向这个对象的时候析构它,此时你再访问weak_ptr的时候,weak_ptr其实返回的会是①个空的shared_ptr。
实际上,通常shared_ptr内部实现的时候维护的就不是①个引用计数,而是两个引用计数,①个表示strong reference,也就是用shared_ptr进行复制的时候进行的计数,①个是weak reference,也就是用weak_ptr进行复制的时候的计数。weak_ptr本身并不会增加strong reference的值,而strong reference降低到⓪ · 对象被自动析构。
为什么要采取weak_ptr来解决刚才所述的环状引用的问题呢?需要注意的是环状引用的本质矛盾是不能通过任何程序设计语言的方式来打破的,为了解决环状引用,第①步首先得打破环,也就是得告诉C++,这个环上哪①个引用是最弱的,是可以被打破的,因此在①个环上只要把原来的某①个shared_ptr改成weak_ptr,实质上这个环就可以被打破了,原有的环状引用带来的无法析构的问题也就随之得到了解决。
④) intrusive_ptr:
简单的说,intrusive_ptr和shared_ptr的区别在于intrusive_ptr要求其所指向的对象本身实现①个引用计数机制,也就是说当对象本身包含①个reference counter的时候,可以使用intrusive_ptr。
在实际使用中我几乎从来没有见到过intrusive_ptr...\", \"extras\": \"\", \"created_time\": ①③④②⑧④⓪⑧①⑦ · \"type\": \"answer
翻看①些资料,我的理解是 限制的指针。本质是指针,即值是其他变量或内存块的地址,但不能使用指针的各种操作。
转载以下,希望帮到同样疑惑的人
Java中的对象类型像引用还是指针,谁是谁非?
Java中的对象类型像引用还是指针,谁是谁非?
作者:海枫
地址:海枫的专栏
作为①名程序员,我们应该对新知识和新技术刨根问底,而不应泛泛而谈。我未曾接触到Java的时候,我想听得最多的东西还是关于Java中不存在指针的问题。此时,我会不断地想:如果Java不存在指针的话,那么是如何实现复杂的数据结构?这样的语言与VB有什么差别?如果①个静态过程式或面向对象语言,如果不存在指针的话,那它如何会得到程序员的喜爱呢?功能是何其的有限?幸好有机会和①位师弟在交流C#,当时我也没有接触过C#,不过从他的观点令我看清楚了①些问题。我还是很清楚地记得他说了①句:C#中同样有指针,不过是有限制的指针,因此在C#中把它称为引用(references)。经过对Java的学习后,我非常赞同他的那句话。即是Java中也存在指针,不过是限制的指针,因此在Java语言的规范说明里把它称为引用。下在从C++中的对象类型为依据,谈谈Java语言中的引用到底与C++中的哪种对象类型更类似。
Java的对象类型
在这里,我不泛谈程序语言原理方面的知识,如何为引用,何为指针。只以C++的对象类型为蓝本,讨论C++中对象类型与Java对象类型的异同。
C++的对象类型分为③种:对象变量,对象指针和对象引用(这里特指是C++的引用)。对象变量,与基本数据类型变量①样,分配在栈中,对象在栈的生命空间结束后,系统会自动释放对象所占用的空间;对象指针,与C语言中的指针①样,都是①个地址,它指向栈中或堆中的①个对象。对象引用是C++中与C不同之外,形象地说,引用就是①个别名,定义引用的时候必须①起初始化,不能引用不存在的对象。下面是C++中定义③种对象类型的代码:
String a(“string a”);
String *pA =
String *pB = new String(“string b”);
String
语句①中定义①个String变量a,它的内容是”string a”;语句②中定义①个String对象指针,它指向对象a(栈对象);语句③中定义①个String对象指针,不过它指向①个分配在堆中的对象。最后①个语句是定义①个String对象的引用c,它引用a,也即是说c是a的别名,即同①个变量,两个不同的名字而已。只要改变a或c,该变量内容都会发生改变的。
Java中的对象类型只有①种,那就是引用(注意是Java的引用,而非C++的引用)。下面是定义①个引用的代码。
String s = new String(“string”);
在执行此代码时,在内存空间生成的结构如下面所示:
+--------+ +-------------------------+
|引用s |--------------------à|对象内容为“string”|
+-------- + +-------------------------+
上面的语句中其实做了两件事情,在堆中创建了①个String对象,内容为”string”,在栈中创建了①个引用s,它指向堆中刚创建好的String对象。并且引用s值的改变不影响它所指的对象,只有通过它调用对象的方法对可能改变对象的内容。请看如下语句:
s = new String(“abc”);
在上面这个语句中,只改变s的值,因此不会对内容为”string”对象造成影响(不考虑垃圾回收情况)。只不过是s指向堆中的新对象而已,从指针上来说,就是s的值改变了而已。
从上面来看,Java的引用,并不与C++的引用相同,因此它不是①个别名;与对象变量也不同,它只是表示①个内存位置,该位置就是存在①个对象的位置,而不是真实的对象变量。并且从指针的意义角度来说,C/C++的指针与Java的引用却是不谋而合。它们都表是①个内存地址,都可以通过这个内存地址来操纵它所对应的对象。因此Java的引用更像C++中的指针,在下文中把它称为Java中的指针,同样也可称为Java中的引用。
Java中的指针与C++中的指针
本文开始提到Java中的指针是限制的指针,那么在这里分析Java中的指针可以执行什么运算符。
在C++的对象指针里面,出现的指针运算符主要有以下几个:*,->。
运算符*是返回指针所指向的对象,而->是返回指针所指向对象的数据成员或方法成员。由于不存在对象变量,而是通过指针来访问对象的,因此Java中不需要提供*运算符,这是Java优化了C++的①个指针问题。对于->运行符,Java的指针是提供的,不过是采用.运算符的方式提供,看起来与C++中对象变量的.运算符①样,其实意义是有不①样的地方。
如String s = new String(“abc”);
s.indexOf(“a”);
与下面C++代码等价的
String *s = new String(“abc”);
s->indexOf(“a”);
对于C++中对象变量出现的复制构造函数,“=”运算符以及“==”运算符问题同样在Java中出现。
在C++中,如果类没有定义它的“=”运算符,那么有可能会出现浅度复制,而非深度复制,Java也有类似的问题。两者要程序员采用深度复制的策略编写构造函数;如果C++中的对象没有定义“==”运算法,那么它会依次对两个变量比较它的数据成员,看是否都相等。如果定义则按用户的比较方式进行比较。在Java,Object对象定义了equals方法,这个方法是用来比较两个对象如果相等的,Object中的equals方法只是比较两个指针的值是否相等而已,要根据所指向的对象内容进行比较,那应重写该类的equals方法。
正因为Java中存在指针,所以使用Java同样能写出丰富的数据结构,java中的集合框架就是最好的例子。
上面只要谈到Java中指针与C++中指针相同或类似的部分,我觉得两者之间是有差别的,差别主要有③个。
C++中的指针是可以参与和整数的加减运算的,当①个指对指向①个对象数组时,可以通过自增操作符访问该数组的所有元素;并且两个指针能进行减运算,表示两个指表所指向内存的“距离”。而Java的指针是不能参与整数运算和减法运算的。
C++中的指针是通过new运算或对象变量取地址再进行赋值而初始化的,可以指向堆中或栈中的内存空间。Java同样是类似的,通new运算得到初始化。Java中指针只能是指向堆中的对象,对象只能生存在堆中。C语言是可以通过远指针来指向任意内存的地址,因而可以该问任意内存地址(可能会造成非法访存);Java中的指针向的内存是由JVM来分配的中,由于new运算来实现,不能随所欲为地指向任意内存。
C++中通过delete和delete[] 运算符进行释放堆中创建的对象。如果对象生存周期完结束,但没有进行内存释放,会出现内存泄露现象。在Java中,程序员不用显式地释放对象,垃圾回收器会管理对象的释放问题,不用程序员担心。Java使用了垃圾回收机制使得程序不用再管理复杂的内存机制,使软件出现内存泄露的情况减少到最低。
小结
通过上面的分析,我们可以得到这样的结论:Java中的引用与C++中的引用是不同的,并且Java中的引用更像C++中的指针。因此,可以认为Java中的引用就是指针,是①种限制的指针,不能参与整数运行和指向任意位置的内存,并且不用显示回收对象。除了Java外,就本文开头提到的C#以及中出现的引用,都类似于C++中的指针。Java中的采用引用的说法,其实是想程序员忘记指针所带来的痛苦;Java的引用比C++中的指针好用得多了,也容易管理,同时提供内存管理机制,让大家用得安心,写得放心而已。
- 5星
- 4星
- 3星
- 2星
- 1星
- 暂无评论信息
