智能指针总结及应用(二)

来源:互联网 发布:阿里小号 知乎 编辑:程序博客网 时间:2024/06/05 16:19
 shared_ptr一些使用技巧:

1、将shared_ptr用于标准容器库有两种方式:
1) 将标准容器库作为shared_ptr管理的对象
例如boost::shared_ptr<std::vector<T> >,使容器可以被安全的共享,用法与普通shared_ptr没区别。
2) 将shared_ptr作为容器的元素
例如std::vector<boost::shared_ptr<T> >,因为shared_ptr支持拷贝构造和赋值操作以及比较操作的语意,符合标准容器对元素的要求,所以可以在容器中安全的容纳元素的指针而不是拷贝。标准容器可以容纳原始指针,例如std::vector<T*>,但是这就丧失了容器的许多好处,因为标准容器库无法自动管理类型为指针的元素,必须编写额外的代码来保证指针最终被正确的删除,而保存shared_ptr作为标准容器库的元素,既保证与存储原始指针几乎一样的功能,而且不用担心资源泄露。
2、以函数方式封装现有的c函数
例如crt的FILE操作函数,fopen/fclose/fread等函数,我们可以使用一些技巧,利用shared_ptr进行封装,从而使
我们不需要调用fclose,让其自动进行内存管理,代码如下:
1) 实现FileClose函数(实际不需要实现该函数,这里实现是为了在资源释放时候打印出相关信息):

    typedef boost::shared_ptr<FILE> FilePtr;    void FileClose(FILE* f)     {       fclose(f);       std::cout << “调用fclose函数释放FILE资源\n”;    }      FilePtr FileOpen(const char* path,const char* mode)    {         //FilePtr fptr(fopen(path,mode),fclose);         //直接使用fclose作为shared_ptr的删除器         FilePtr fptr(fopen(path,mode),FileClose);         //使用我们的包装删除器,在释放资源时候打印出相关信息        return fptr;     }

3、用c++桥接设计模式来封装现有的c函数,隐藏实现细节

    在c++的.h文件中声明如下类:   class FileSharedPtr   {      private:          class impl;//很重要一点,前向申明实现类,具体实现在.cpp文件中,隐藏实现细节         boost::shared_ptr<impl> pimpl; //shared_ptr作为私有成员变量      public:        FileSharedPtr(char const * name, char const * mode);        void Read(void * data, size_t size);        };        在c++的.cpp文件中实现如下类:class FileSharedPtr::impl{private:    impl(impl const &){}    impl & operator=(impl const &){}    FILE* f;public:    impl(char const * name, char const * mode){f = fopen(name,mode);}    ~impl()    {          int result = fclose(f);          printf("invoke FileSharedPtr::impl 析构函数result = %d\n",result);    }    void read(void * data, size_t size) { fread(data,1,size,f) }};FileSharedPtr::FileSharedPtr(const char *name, const char *mode) : pimpl(new FileSharedPtr::impl(name,mode)){}void FileSharedPtr::Read(void* data,size_t size){ pimpl->read(data,size); }void Test_CPP_File_Ptr(){    FileSharedPtr ptr(“memory.log”,“r”);//引用计数为1    FileSharedPtr ptr2 = ptr;//引用计数为2    char data[100];    ptr.Read(data,100);    printf("%s\n",data);} //析构ptr2引用计数为1,再析构ptr,引用计数为0,释放内存,无泄漏

4、使用面向接口编程方式隐藏实现

在c++的.h文件中声明如下接口:class IPrinter{   public:    virtual void Print() = 0;protected://受保护的虚拟析构函数,导致本类必须被继承,也不能调用delete操作符。    virtual ~IPrinter()     {          printf("invoke IPrinter virtual 析构函数\n");    }};typedef boost::shared_ptr<IPrinter> PrinterPtr;PrinterPtr CreatePrinter();//工厂方法,创建IPrinter智能指针。
在c++的.cpp文件中声明如下实现类 :(很重要一点,实现类都是在cpp文件中的,必须要注意这一点) class Printer : public IPrinter{private:    FILE* f;public:    //使用createPrinter时候,调用实现类的构造函数,返回的是IPrinter的shared_ptr    Printer(const char* path,const char* mode){f = fopen(path,mode);    }    //实现类的析构是public,但是接口类的是protected,但是shared_ptr在析构时候会自动调用实现类的析构 //且再次调用基类受保护的虚拟析构函数。通过这种机制我们可以完全封闭掉new和delete操作符,只能使用  //公开的工厂方法函数返回接口智能指针,而且不需要也无法调用析构函数,完全由shared_ptr来管理内存。  //这样我们在整个程序中都没有原始指针的概念,从而不会忘记调用delte操作而导致内存泄露。    ~Printer() {  fclose(f); printf("invoke Printer 析构函数result = %d\n",result); }     void Print() { char data[100]; fread(data,1,100,f); printf("%s\n",data); rewind(f); }};PrinterPtr CreatePrinter() {  PrinterPtr ptr(new Printer("memory.log","r") ; return ptr};

5、使用shared_ptr持有一个具有侵入式的引用计数的对象

如Com对象或intrusive_ptr等需要手动增减引用计数的对象。

假设我们要将shared_ptr用于一个COM对象。

 1)我们在头文件中引入<boost/mem_fn.hpp> 头文件前定义宏:          #define BOOST_MEM_FN_ENABLE_STDCALL   2) #include <boost/mem_fn.hpp>  3) 我们定义一个函数,例如  shared_ptr<IWhatever> make_shared_from_COM(IWhatever * p)、     {              p->AddRef();             //注意mem_fn仿函数的用法,它用于成员函数,&类名::成员函数名            shared_ptr<IWhatever> pw(p, mem_fn(&IWhatever::Release));             return pw;      }      一旦使用shared_ptr享有com的接口指针所有权后,com的引用计数被shared_ptr所接管,因此所有引用计数增减都是由shared_ptr来进行

6、使用shared_ptr持有一个win32 handle

例如win32使用HANDLE来代表内核对象,所有内核对象都使用CreateXXX创建一个句柄,而使用CloseHandle来释放一个句柄。

    typedef void* HANDLE;//win32 HANDLE的内部定义,在windows中可以看到    HANDLE CreateMutex();    BOOL      CloseHandle(HANDLE);   // 使用shared_ptr来管理上述类型的HANDLE,代码如下:    typedef shared_ptr<void> handle;.    BOOL MyCloseHandleWrap(HANDLE h)     {           std::cout << “调用win32 CloseHandle API\n”;           return CloseHandle(h);    }    handle  createMutexHandle()    {        shared_ptr<void>   ptr(CreateMutex(NULL,FALSE,NULL),MyCloseHandleWrap/*CloseHandle*/);    }

7、使用shared_ptr持有一个静态分配的对象

  有时候我们需要使用shared_ptr来管理一个静态分配的对象或全局对象,例如 static X xobj;由于静态或全局对象的析构是在程序结束时候自动进行的,我们不能够在shared_ptr中自动调用delete操作符,因此我们可以实现一个NULL析构器来实现该目的struct null_deleter//仿函数对象{  //重载函数调用操作符()   void operator()(void* const ) const  { } //没有任何代码 };shared_ptr<X> CreateX(){    shared_ptr<X> px(&xobj,null_deleter());    return px;}

8、使用shared_ptr 持有任意对象的所有权

1) 使用shared_ptr<void>来获得任何对象的所有权:    在该shared_ptr<void>离开作用域时候,会自动调用该shared_ptr所享有的实际对象的析构函数,2) 如果要使用实际对象的相关成员函数:    需要使用:    boost::static_pointer_cast    boost::dynamic_pointer_cast    boost::const_pointer_cast    boost::reinterpret_pointer_cast    这四个转型模void Test_Create_Void_Ptr_From_String(){boost::shared_ptr<void> ptr(new std::string(“voidPtr create from std::string”));//ptr计数为1boost::shared_ptr<void> ptr2 = ptr;//ptr和ptr2计数都为2std::cout << ptr2.use_count() << std::endl;boost::shared_ptr<std::string> ptrStr = boost::static_pointer_cast<std::string>(ptr2);//转型引用计数也加         //ptr,ptr2和ptrStr的引用计数都为3std::cout << ptrStr.use_count() << std::endl;std::cout << ptrStr->c_str() << std::endl;         //退出作用域前,会自动调用void指向的实际对象的析构函数,如果引用计数为0时,会销毁内存,无泄漏

}板函数。

总结:
1、可以通过shared_ptr以及删除器来实现各种内存管理方式:
2、通过shared_ptr和weak_ptr我们获得整套内存管理的解决方案:
通过面向对象方式的编程模式(桥接模式,面向接口模式,受保护构造和析构,工厂方法等等),我们可以全部消灭掉原始指针,封闭掉new/delete操作符,使客户端调用完全不关心内存的具体使用,不需要调用内存分配(new)和(delete)。
3、所付出的代价仅仅就是比原始指针多4个byte

其他智能指针:
1、scoped_ptr:
不允许拷贝,赋值,因此不能用于stl标准容器,并且仅用于局部作用域中(例如函数中),除了重载* 和->操作符外,也没有定义其操作,基本操作类似shared_ptr.
2、scoped_array和shared_array:
用于数组,实际上像shared_ptr可以和stl容器搭配使用,数组的用途也不太大
3、intrusive_ptr:
侵入式智能指针,由于在boost::smart_ptr库中,shared_ptr是首选,可以满足基本所有情况,boost官方也不建议使用intrusive_ptr

make_shared模版工厂函数
1、该函数是一个辅助方法:
用于不需要使用删除器的情况下(仅仅使用new/delete进行内存分配和析构的类上面)。由于shared_ptr显式的消除了delete操作符的调用,因此使用该函数也可以显式的消除了new操作符的调用。
2、调用该函数比直接创建shared_ptr对象的方式快且高效:
因为它内部仅分配一次内存,消除了shared_ptr构造时的开销,建议在满足情况的基础上尽量使用该函数。
3、可变模版参数特性;

c++中四种风格的指针顺便复习一下:
C 风格(C-style)强制转型如下:
(T) expression 或
T(expression) //函数风格(Function-style)
两种形式之间没有本质上的不同。
对于具有转换的简单类型而言C 风格转型工作得很好。然而,这样的转换符也能不分皂白地应用于类(class)和类的指针。ANSI-C++标准定义了四个新的转换符:reinterpret_cast, static_cast, dynamic_cast和const_cast,目的在于控制类(class)之间的类型转换。
1.1 reinpreter_cast
用法:reinpreter_cast (expression)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。
这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。reinpreter_cast是特意用于底层的强制转型,导致实现依赖(就是说,不可移植)的结果。
int n=9;
// reinterpret_cast 仅仅是复制 n 的比特位到 d,因此d 包含无用值。
double d=reinterpret_cast< double & > (n);
1.2 const_cast
用法:const_cast (expression)
用于修改类型的const或volatile属性。除了const 或volatile修饰之外,type_id和expression的类型是一样的,一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型,而C不提供消除const的机制(已验证)。
常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
1.3 static_cast
用法:static_cast < type-id > ( expression )
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它允许执行任意的隐式转换和相反转换动作。主要有如下几种用法:
1)用于基本数据类型之间的转换,如把int转换成char,non-const 对象转型为 const 对象(这里相反方向不可以,C++只有const_cast可以)。
2)把空指针转换成目标类型的指针。(之前的做法是用强制转换(type-id*))
3)把任何类型的表达式转换成void类型。
4)应用到类的指针上,它允许子类类型的指针转换为父类类型的指针(upercasting这是一个有效的隐式转换);也能够执行相反动作,即转换父类为它的子类(downcasting),这种转换的安全性需要开发人员来保证(主要是在非上下转型中)。
class Base {};
class Derived : public Base {};
Base *a = new Base;
Derived *b = NULL;
b = static_cast< Derived *>(a); //可以通过编译,但存在安全隐患(如访问//Derived的成员)
注意:
1.static_cast不能转换掉expression的const、volitale、或者__unaligned属性。
2.在非基本类型或上下转型中,被转换的父类需要检查是否与目的类型相一致,否则,如果在两个完全不相干的类之间进行转换,将会导致编译出错。
1.4 dynamic_cast
只用于对象的指针和引用,主要用于执行“安全的向下转型”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型。
当用于多态类型时(包含虚函数),它允许任意的隐式类型转换以及相反过程。不过,与static_cast不同,在后一种情况里(即隐式转换的相反过程),dynamic_cast根据RTTI信息检查操作是否有效。即在转换时dynamic_cast会检查转换是否能返回一个被请求的有效的完整对象。这种检查不是语法上的,而是真实情况的检查。检测在运行时进行,如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL。
先看RTTI相关部分,通常,许多编译器都是通过vtable找到对象的RTTI信息的,这也就意味着,如果基类没有虚函数,也就无法判断一个基类指针变量所指对象的真实类型, 这时候dynamic_cast只能用来做安全的转换(upercasting),如从派生类指针转换成基类指针,而这种转换其实并不需要dynamic_cast参与。
1.5 小结
四种类型转换操作符对于隐式的类型转换没有必要。
static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随着不安全性。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时(基类需要包含虚函数),dynamic_cast具有类型检查的功能,牺牲了效率,但比static_cast安全。

注明: 关于shared_ptr的系列讲解时来自某课程网。

0 0
原创粉丝点击