智能指针学习
2016-09-13 10:44
51 查看
包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象!
将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。指针成员默认具有与指针对象同样的行为。
大多数C++类采用以下三种方法之一管理指针成员:
1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制!
2)类可以实现所谓的“智能指针”行为:通过对共享指针的对象进行计数,指针所指向的对象是共享的,但类能够防止悬垂指针。
3)类采取值型行为:每个对象在建立的时候,不管是初始化或者复制构造,指针所指向的对象是唯一的,有每个类对象独立管理。
1 常规指针:
因为HasPtr类没有定义复制构造函数,所以复制一个HasPtr对象将复制两个成员,因为采取的常规的合成构造函数
2 智能指针
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。auto_ptr
即是一种常见的智能指针。
比如上面类hasPtr中的成员ptr就可以跟引用计数绑定到一起,然后写到同一个类中:
将所有的成员都设置成为private:我们不希望普通用户使用U_Ptr类,所以他没有任何public成员!
HasPtr类需要一个析构函数来删除指针。但是,析构函数不能无条件的删除指针。”
条件就是引用计数。如果该对象被两个指针所指,那么删除其中一个指针,并不会调用该指针的析构函数,因为此时还有另外一个指针指向该对象。看来,智能指针主要是预防不当的析构行为,防止出现悬垂指针。
如上图所示,HasPtr就是智能指针,U_Ptr为计数器;里面有个变量use和指针ip,use记录了*ip对象被多少个HasPtr对象所指。假设现在又两个HasPtr对象p1、p2指向了U_Ptr,那么现在我delete p1,use变量将自减1, U_Ptr不会析构,那么U_Ptr指向的对象也不会析构,那么p2仍然指向了原来的对象,而不会变成一个悬空指针。当delete p2的时候,use变量将自减1,为0。此时,U_Ptr对象进行析构,那么U_Ptr指向的对象也进行析构,保证不会出现内存泄露。
新的HasPtr类保存一个指向U_Ptr对象的指针,U_Ptr对象指向实际的int基础对象:
复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一U_Ptr对象,该U_Ptr对象的使用计数加1。
析构函数将检查U_Ptr基础对象的使用计数。如果使用计数为0,则这是最后一个指向该U_Ptr对象的HasPtr对象,在这种情况下,HasPtr析构函数删除其U_Ptr指针。删除该指针将引起对U_Ptr析构函数的调用,U_Ptr析构函数删除int基础对象。
赋值操作符在减少左操作数的使用计数之前使rhs的使 用计数加1,从而防止自身赋值。如果左右操作数相同,赋值操作符的效果将是U_Ptr基础对象的使用计数加1之后立即减 1。
从结果中我们可以看到:
1.先创建一个HasPtr 对象ptr1,这会调用构造函数,引用计数初始化为1. 然后调用复制构造函数,可以看到指针的地址是一样的,
只是use变为2.
2.当调用ptr1.set_ptr(new_obj);后 两个对象的指针值都相同(其实是同一个指针),说明它们共享这个指针
3.在return 0;语句之前可以看到,先后调用两个对象的析构函数,分别导致智能指针U_ptr的use为2, 1,然后调用U_ptr的析构函数释放掉内存。
3 定义值型类
[cpp] view
plaincopy
class HasPtr
{
private:
HasPtr(const int &p,int i):ptr(new int(p)),val(i) {}
//复制控制
HasPtr(const HasPtr &rhs):ptr(new int(*rhs.ptr)),val(rhs.val) {}
//<复制构造函数中同样申请一个指针
HasPtr &operator=(const HasPtr &rhs);
~HasPtr()
{
delete ptr;
}
........
public:
int *ptr;
int val;
};
复制构造函数不再复制指针,它将分配一个新的int对象,并初始化该对象以保存与被复制对象相同的值。每个对象都保存属于自己的int值的不同副本。因为每个对象保存自己的副本,所以析构函数将无条件删除指针。
赋值操作符也因而不用分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:
[cpp] view
plaincopy
HasPtr &HasPtr::operator=(const HasPtr &rhs)
{
*ptr = *rhs.ptr; //<初始化对象就可以了
val = rhs.val;
return *this;
}
即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。本例中,即使左右操作数相同,操作本质上也是安全的,因此,不需要显式检查自身赋值。
继续学习文章:http://www.cnblogs.com/TenosDoIt/p/3456704.html
将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。指针成员默认具有与指针对象同样的行为。
大多数C++类采用以下三种方法之一管理指针成员:
1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制!
2)类可以实现所谓的“智能指针”行为:通过对共享指针的对象进行计数,指针所指向的对象是共享的,但类能够防止悬垂指针。
3)类采取值型行为:每个对象在建立的时候,不管是初始化或者复制构造,指针所指向的对象是唯一的,有每个类对象独立管理。
1 常规指针:
#ifndef _HASPTR_H_ #define _HASPTR_H_ /************************************************************************/ /* * 带指针成员的简单类,其指针共享同一对象,因而会出现悬垂指针 * * date:2016/9/13 * */ /************************************************************************/ class HasPtr { public: HasPtr(int *p , int i):ptr(p) , val(i){}; ~HasPtr(void){ } public: int *get_ptr() const { return ptr; } int get_val() const { return val;} void set_ptr( int *p ) { ptr =p;} void set_val( int i) { val = i;} int get_ptr_val() const { return *ptr;} void set_ptr_val( int i) { *ptr = i;} private: int *ptr; int val; }; #endif
因为HasPtr类没有定义复制构造函数,所以复制一个HasPtr对象将复制两个成员,因为采取的常规的合成构造函数
#include <iostream> #include "hasPtr.h" using namespace std; int main () { int obj=0; HasPtr ptr1(&obj,42); //copy 后 &ptr1.ptr==&ptr2.ptr cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl; HasPtr ptr2(ptr1); cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl; //因为ptr2是ptr1 copy过来的,而set_ptr_val()函数仅仅修改的是*ptr的值 //ptr 的地址仍然不变,ptr1和ptr2还是相同 ptr1.set_ptr_val(34); cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl; cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl; //改变了指针本身的值,因此两个指针所指向的内容不同 int m=44; ptr1.set_ptr(&m); cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl; cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl; ////悬垂指针 ,因为ip和ptr中的指针指向同一对象,删除该对象,ptr中 //的指针不再指向有效对象了 //int *ip=new int (43); //HasPtr ptr(ip,10); //delete ip; // //ptr.set_ptr_val(0); //cout<<ptr.get_ptr()<<'\t'<<ptr.get_ptr_val()<<'\t'<<ptr.get_val()<<endl; system("pause"); return 0; }
2 智能指针
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。auto_ptr
即是一种常见的智能指针。
比如上面类hasPtr中的成员ptr就可以跟引用计数绑定到一起,然后写到同一个类中:
<span style="font-family:Verdana;color:#333333;">/************************************************************************/ /* * 定义一个单独的具体类用以封装使用计数和相关指针 * */ /************************************************************************/ //class U_Ptr , private class for use by hasPtr only class U_Ptr { private: //将HasPtr设置成为友元类,使其成员可以访问U_Ptr的成员 friend class HasPtr; U_Ptr(int *p): ip(p) , use(1) {} ~U_Ptr() { delete ip; } private: int *ip; size_t use; };</span>
将所有的成员都设置成为private:我们不希望普通用户使用U_Ptr类,所以他没有任何public成员!
HasPtr类需要一个析构函数来删除指针。但是,析构函数不能无条件的删除指针。”
条件就是引用计数。如果该对象被两个指针所指,那么删除其中一个指针,并不会调用该指针的析构函数,因为此时还有另外一个指针指向该对象。看来,智能指针主要是预防不当的析构行为,防止出现悬垂指针。
如上图所示,HasPtr就是智能指针,U_Ptr为计数器;里面有个变量use和指针ip,use记录了*ip对象被多少个HasPtr对象所指。假设现在又两个HasPtr对象p1、p2指向了U_Ptr,那么现在我delete p1,use变量将自减1, U_Ptr不会析构,那么U_Ptr指向的对象也不会析构,那么p2仍然指向了原来的对象,而不会变成一个悬空指针。当delete p2的时候,use变量将自减1,为0。此时,U_Ptr对象进行析构,那么U_Ptr指向的对象也进行析构,保证不会出现内存泄露。
新的HasPtr类保存一个指向U_Ptr对象的指针,U_Ptr对象指向实际的int基础对象:
<span style="font-family:Verdana;color:#333333;">class Hasptr { public: Hasptr(int *p,int i):ptr(new U_Ptr(p)),val(i) { cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl; } //赋值控制成员 Hasptr(const Hasptr &org):ptr(org.ptr),val(org.val) { ++ptr->use; cout << "HasPtr copy constructor called ! " << "use = " << ptr->use << endl; } Hasptr &operator=(const Hasptr &rhs){ // 增加右操作数中的使用计数 ++rhs.ptr->use; // 将左操作数对象的使用计数减1,若该对象的使用计数减至0,则删除该对象 if(-- ptr ->use==0) delete ptr; ptr=rhs.ptr ; val=rhs.val ; return *this; } ~Hasptr() { cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl; if(--ptr->use==0) delete ptr; } //return value int *get_ptr() const { return ptr->ip; } int get_val() const { return val; } //non const members changes the indicated members void set_ptr(int *p) { ptr->ip=p; } void set_val(int i) { val=i; } // void set_ptr_val(int val) { *ptr->ip=val; } int get_ptr_val() const { return *ptr->ip; } private: int val; U_Ptr *ptr; };</span>
复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一U_Ptr对象,该U_Ptr对象的使用计数加1。
析构函数将检查U_Ptr基础对象的使用计数。如果使用计数为0,则这是最后一个指向该U_Ptr对象的HasPtr对象,在这种情况下,HasPtr析构函数删除其U_Ptr指针。删除该指针将引起对U_Ptr析构函数的调用,U_Ptr析构函数删除int基础对象。
赋值操作符在减少左操作数的使用计数之前使rhs的使 用计数加1,从而防止自身赋值。如果左右操作数相同,赋值操作符的效果将是U_Ptr基础对象的使用计数加1之后立即减 1。
<span style="font-size:14px;">#include <iostream> #include "Hasptr.h" using namespace std; int main () { int *obj =new int(12); Hasptr ptr1(obj,42); //注意: obj一定是在堆上申请的内存,不能是在栈上 //copy 后 &ptr1.ptr==&ptr2.ptr cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl; Hasptr ptr2(ptr1); cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl; //因为ptr2是ptr1 copy过来的,而set_ptr_val()函数仅仅修改的是*ptr的值 //ptr 的地址仍然不变,ptr1和ptr2还是相同 ptr1.set_ptr_val(34); cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl; cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl; //这里使用了智能指针后, 只是引用计数加1,指针还是同一个,所以值相同 int *new_obj = new int(44); //注意: obj一定是在堆上申请的内存,不能是在栈上 ptr1.set_ptr(new_obj); cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl; cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl; int *ip=new int (43); Hasptr ptr(ip,10); /* * 我们不能直接调用 delete ip , 因为这样直接释放掉堆上的内容,导致智能指针ptr * 指向的内容也不复存在 */ //delete ip; // ptr.set_ptr_val(0); cout<<ptr.get_ptr()<<'\t'<<ptr.get_ptr_val()<<'\t'<<ptr.get_val()<<endl; system("pause"); return 0; }</span>其结果如下:
从结果中我们可以看到:
1.先创建一个HasPtr 对象ptr1,这会调用构造函数,引用计数初始化为1. 然后调用复制构造函数,可以看到指针的地址是一样的,
只是use变为2.
2.当调用ptr1.set_ptr(new_obj);后 两个对象的指针值都相同(其实是同一个指针),说明它们共享这个指针
3.在return 0;语句之前可以看到,先后调用两个对象的析构函数,分别导致智能指针U_ptr的use为2, 1,然后调用U_ptr的析构函数释放掉内存。
3 定义值型类
[cpp] view
plaincopy
class HasPtr
{
private:
HasPtr(const int &p,int i):ptr(new int(p)),val(i) {}
//复制控制
HasPtr(const HasPtr &rhs):ptr(new int(*rhs.ptr)),val(rhs.val) {}
//<复制构造函数中同样申请一个指针
HasPtr &operator=(const HasPtr &rhs);
~HasPtr()
{
delete ptr;
}
........
public:
int *ptr;
int val;
};
复制构造函数不再复制指针,它将分配一个新的int对象,并初始化该对象以保存与被复制对象相同的值。每个对象都保存属于自己的int值的不同副本。因为每个对象保存自己的副本,所以析构函数将无条件删除指针。
赋值操作符也因而不用分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:
[cpp] view
plaincopy
HasPtr &HasPtr::operator=(const HasPtr &rhs)
{
*ptr = *rhs.ptr; //<初始化对象就可以了
val = rhs.val;
return *this;
}
即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。本例中,即使左右操作数相同,操作本质上也是安全的,因此,不需要显式检查自身赋值。
继续学习文章:http://www.cnblogs.com/TenosDoIt/p/3456704.html
相关文章推荐
- 智能指针学习
- Boost学习系列2-智能指针(上)
- OSG学习笔记1——智能指针
- Qt 智能指针学习(7种QT的特有指针)
- Qt 智能指针学习
- Android智能指针学习笔记
- C++11常用特性学习-类型安全(强类型枚举类/智能指针)
- C++ Primer 学习笔记——动态内存和智能指针(2)
- Qt 智能指针学习
- Boost库智能指针学习
- Qt 智能指针学习
- [转]Qt 智能指针学习
- Qt 智能指针学习(7种指针)
- Qt 智能指针学习
- Boost库学习—智能指针Shared_ptr学习01
- C++学习笔记(八) 智能指针
- Boost库智能指针学习
- Qt 智能指针学习(7种QT智能指针和4种std智能指针)
- 智能指针学习
- c++ primer 智能指针学习心得