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计数为1
boost::shared_ptr<void> ptr2 = ptr;//ptr和ptr2计数都为2
std::cout << ptr2.use_count() << std::endl;
boost::shared_ptr<std::string> ptrStr = boost::static_pointer_cast<std::string>(ptr2);//转型引用计数也加
//ptr,ptr2和ptrStr的引用计数都为3
std::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的系列讲解时来自某课程网。