Boost智能指针使用总结
内存管理是一个比较繁琐的问题,C++中有两个实现方案: 垃圾回收机制和智能指针。垃圾回收机制因为性能等原因不被C++的大佬们推崇, 而智能指针被认为是解决C++内存问题的最优方案。
1. 智能指针定义
一个智能指针就是一个C++的对象, 这对象的行为像一个指针,但是它却可以在其不需要的时候自动删除。注意这个“其不需要的时候”, 这可不是一个精确的定义。这个不需要的时候可以指好多方面:局部变量退出函数作用域、类的对象被析构……。所以boost定义了多个不同的智能指针来管理不同的场景。
shared_ptr<T> | 内部维护一个引用计数器来判断此指针是不是需要被释放。是boost中最常用的智能指针了。 |
scoped_ptr<t> | 当这个指针的作用域消失之后自动释放 |
intrusive_ptr<T> | 也维护一个引用计数器,比shared_ptr有更好的性能。但是要求T自己提供这个计数器。 |
weak_ptr<T> | 弱指针,要和shared_ptr 结合使用 |
shared_array<T> | 和shared_ptr相似,但是访问的是数组 |
scoped_array<T> | 和scoped_ptr相似,但是访问的是数组 |
2. Boost::scoped_ptr<T>
2.1 定义
scoped_ptr 是boost中最简单的智能指针。scoped_ptr的目的也是很简单, 当一个指针离开其作用域时候,释放相关资源。特别注意的一定就是scoped_ptr 不能共享指针的所有权也不能转移所有权。也就是说这个内存地址就只能给的声明的变量用,不能给其他使用。
2.2 特点
(1)scoped_ptr的效率和空间的消耗内置的指针差不多
(2)scoped_ptr不能用于管理数组对象,不能指向一块能够动态增长的内存区域(用scoped_array代替)
(3)scoped_ptr不能转换所有权,因此不能作为函数的返回值
(4)scoped_ptr不能共享所有权,因此不能用于stl的容器中(用shared_ptr代替)
2.3 使用原则
(1)在可能有异常抛出的作用域里使用指针
(2)函数里有几条控制路径
(3)动态分配对象的生存期应被限制于特定的作用域内
(4)异常安全非常重要时(总应如此!)
2.4 例子
class test
{
public:
void print()
{
cout << "test print now" <<endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
boost::scoped_ptr<test> x(new test);
x->print();
return ;
}
3.Boost::shared_ptr<T>
3.1 定义
boost::shared_ptr是可以共享所有权的智能指针.
3.2 特点
(1)boost::shared_ptr在内部维护一个引用计数器, 当有一个指针指向这块内存区域是引用计数+1, 反之-1, 如果没有任何指针指向这块区域, 引用计数器为0,释放内存区域。
(2)boost::shared_ptr可以共享和转移所有权
(3)boost::shared_ptr可以被标准库的容器所使用
(4)boost::shared_ptr是线程安全的,这点在多线程程序中也非常重要
(5)boost::shared_ptr不能指向一块动态增长的内存(用share_array代替)
3.3 使用原则
(1)避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
(2)shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)
(3)不要构造一个临时的shared_ptr作为函数的参数
3.4 例子
int _tmain(int argc, _TCHAR* argv[])
{
boost::shared_ptr<test> ptr_1(new test);
ptr_1->print();//引用计数为1
boost::shared_ptr<test> ptr_2 = ptr_1;
ptr_2->print();//引用计数为2
ptr_1->print();// 引用计数还是为2
return ;
}
4. Boost::intrusive_ptr<T>
4.1 定义
boost::intrusive_ptr一种“侵入式”的引用计数指针,它实际并不提供引用计数功能,而是要求被存储的对象自己实现引用计数功能,并提供intrusive_ptr_add_ref和intrusive_ptr_release函数接口供boost::intrusive_ptr调用。
4.2 使用原则
(1)你需要把 this当作智能指针来使用
(2)已有代码使用或提供了插入式的引用计数
(3)智能指针的大小必须与裸指针的大小相等
4.3 例子
下面通过一个具体的例子来说明boost::intrusive_ptr的用法,首先实现一个基类intrusive_ptr_base,定义intrusive_ptr_add_ref和intrusive_ptr_release函数来提供引用计数功能。
/**
* intrusive_ptr_base基类,提供intrusive_ptr_add_ref()和intrusive_ptr_release()函数来提供引用计数功能;
* 使用boost::intrusive_ptr指针存储的用户类类型必须继承自intrusive_ptr_base基类。
*/
#include <ostream>
#include <boost/checked_delete.hpp>
#include <boost/detail/atomic_count.hpp> template<class T>
class intrusive_ptr_base {
public:
/**
* 缺省构造函数
*/
intrusive_ptr_base(): ref_count() {
std::cout << " Default constructor " << std::endl;
} /**
* 不允许拷贝构造,只能使用intrusive_ptr来构造另一个intrusive_ptr
*/
intrusive_ptr_base(intrusive_ptr_base<T> const&): ref_count() {
std::cout << " Copy constructor..." << std::endl;
} /**
* 不允许进行赋值操作
*/
intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) {
std::cout << " Assignment operator..." << std::endl;
return *this;
} /**
* 递增引用计数(放到基类中以便compiler能找到,否则需要放到boost名字空间中)
*/
friend void intrusive_ptr_add_ref(intrusive_ptr_base<T> const* s) {
std::cout << " intrusive_ptr_add_ref..." << std::endl;
assert(s->ref_count >= );
assert(s != );
++s->ref_count;
} /**
* 递减引用计数
*/
friend void intrusive_ptr_release(intrusive_ptr_base<T> const* s) {
std::cout << " intrusive_ptr_release..." << std::endl;
assert(s->ref_count > );
assert(s != );
if (--s->ref_count == )
boost::checked_delete(static_cast<T const*>(s)); //s的实际类型就是T,intrusive_ptr_base<T>为基类
} /**
* 类似于shared_from_this()函数
*/
boost::intrusive_ptr<T> self() {
return boost::intrusive_ptr<T>((T*)this);
} boost::intrusive_ptr<const T> self() const {
return boost::intrusive_ptr<const T>((T const*)this);
} int refcount() const {
return ref_count;
} private:
///should be modifiable even from const intrusive_ptr objects
mutable boost::detail::atomic_count ref_count; };
用户类类型需要继承intrusive_ptr_base基类,以便具有引用计数功能。
#include <iostream>
#include <string>
#include <boost/intrusive_ptr.hpp>
#include "intrusive_ptr_base.hpp" /**
* 用户类类型继承自intrusive_ptr_base,该实现方式类似于boost::enable_shared_from_this<Y>
*/
class Connection : public intrusive_ptr_base< Connection > {
public:
/**
* 构造函数,调用intrusive_ptr_base< Connection >的缺省构造函数来初始化对象的基类部分
*/
Connection(int id, std::string tag):
connection_id( id ), connection_tag( tag ) {} /**
* 拷贝构造函数,只复制自身数据,不能复制引用计数部分
*/
Connection(const Connection& rhs):
connection_id( rhs.connection_id ), connection_tag( rhs.connection_tag) {} /**
* 赋值操作,同样不能复制引用计数部分
*/
const Connection operator=( const Connection& rhs) {
if (this != &rhs) {
connection_id = rhs.connection_id;
connection_tag = rhs.connection_tag;
} return *this;
} private:
int connection_id;
std::string connection_tag;
}; int main() {
std::cout << "Create an intrusive ptr" << std::endl;
boost::intrusive_ptr< Connection > con0 (new Connection(, "sss") ); //调用intrusive_ptr_add_ref()递增引用计数
std::cout << "Create an intrusive ptr. Refcount = " << con0->refcount() << std::endl; boost::intrusive_ptr< Connection > con1 (con0); //调用intrusive_ptr_add_ref()
std::cout << "Create an intrusive ptr. Refcount = " << con1->refcount() << std::endl;
boost::intrusive_ptr< Connection > con2 = con0; //调用intrusive_ptr_add_ref()
std::cout << "Create an intrusive ptr. Refcount = " << con2->refcount() << std::endl; std::cout << "Destroy an intrusive ptr" << std::endl; return ;
}
程序运行输出:
Create an intrusive ptr
Default constructor
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 1
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 2
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 3
Destroy an intrusive ptr
intrusive_ptr_release...
intrusive_ptr_release...
intrusive_ptr_release...
4.4 boost::intrusive_ptr与boost::shared_ptr区别
使用boost::shared_ptr用户类本身不需要具有引用计数功能,而是由boost::shared_ptr来提供;使用boost::shared_ptr的一大陷阱就是用一个raw pointer多次创建boost::shared_ptr,这将导致boost::shared_ptr析构时该raw pointer被多次销毁当。即不能如下使用:
int *a = new int();
boost::shared_ptr ptr1(a);
boost::shared_ptr ptr2(a); //错误!
boost::intrusive_ptr完全具备boost::shared_ptr的功能,且不存在shared_ptr的问题,即可以利用raw pointer创建多个intrusive _ptr,其原因就在于引用计数的ref_count对象,shared_ptr是放在shared_ptr结构里,而目标对象T通过继承intrusive_ptr_base将引用计数作为T对象的内部成员变量,就不会出现同一个对象有两个引用计数器的情况出现。
那么为什么通常鼓励大家使用shared_ptr,而不是intrusive_ptr呢, 在于shared_ptr不是侵入性的,可以指向任意类型的对象; 而intrusive_ptr所要指向的对象,需要继承intrusive_ptr_base,即使不需要,引用计数成员也会被创建。
4.5 结论
如果创建新类且需要进行传递,则继承intrusive_ptr_base,使用intrusive_ptr。
5. Boost::weak_ptr<T>
5.1 定义
weak_ptr 就是一个弱指针。weak_ptr 被shared_ptr控制, 它可以通过share_ptr的构造函数或者lock成员函数转化为share_ptr。
5.2 特点
(1)weak_ptr的一个最大特点就是它共享一个share_ptr的内存
(2)无论是构造还是析构一个weak_ptr都不会影响引用计数器
5.3 弱引用与强引用
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
boost::weak_ptr boost::weak_ptr<T>是boost提供的一个弱引用的智能指针,它的声明可以简化如下: namespace boost { template<typename T> class weak_ptr {
public:
template <typename Y>
weak_ptr(const shared_ptr<Y>& r); weak_ptr(const weak_ptr& r); ~weak_ptr(); T* get() const;
bool expired() const;
shared_ptr<T> lock() const;
};
}
可以看到,boost::weak_ptr必须从一个boost::share_ptr或另一个boost::weak_ptr转换而来,这也说明,进行该对象的内存管理的是那个强引用的boost::share_ptr。boost::weak_ptr只是提供了对管理对象的一个访问手段。
boost::weak_ptr除了对所管理对象的基本访问功能(通过get()函数)外,还有两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用指针。
5.4 循环引用
5.4.1 循环定义
引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。
5.4.2 循环引用例子
#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp> class parent;
class children; typedef boost::shared_ptr<parent> parent_ptr;
typedef boost::shared_ptr<children> children_ptr; class parent
{
public:
~parent() { std::cout <<"destroying parent\n"; } public:
children_ptr children;
}; class children
{
public:
~children() { std::cout <<"destroying children\n"; } public:
parent_ptr parent;
}; void test()
{
parent_ptr father(new parent());
children_ptr son(new children); father->children = son;
son->parent = father;
} void main()
{
std::cout<<"begin test...\n";
test();
std::cout<<"end test.\n";
}
运行该程序可以看到,即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的内存泄漏。
一般来讲,解除这种循环引用有下面有三种可行的方法:
(1)当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
(2)当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
(3)使用弱引用的智能指针打破这种循环引用。
虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。这里主要介绍一下第三种方法和boost中的弱引用的智能指针boost::weak_ptr。
5.4.3 通过boost::weak_ptr来打破循环引用
由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。对于上面的那个例子来说,只要把children的定义改为如下方式,即可解除循环引用:
class children
{
public:
~children() { std::cout <<"destroying children\n"; } public:
boost::weak_ptr<parent> parent;
};
最后值得一提的是,虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。毕竟,对于C++来说,由于没有垃圾回收机制,内存泄漏对每一个程序员来说都是一个非常头痛的问题。
5.5 使用原则
(1)要打破递归的依赖关系
(2)使用一个共享的资源而不需要共享所有权
(3)避免悬空的指针
(4)shared_ptr构造weak_ptr时,weak_ptr所指内存为空会抛出异常,而weak_ptr的lock()成员不会抛出异常但会返回个空指针,根据自己需求选择
5.6 例子
int _tmain(int argc, _TCHAR* argv[])
{
boost::shared_ptr<test> sharePtr(new test);;
boost::weak_ptr<test> weakPtr(sharePtr);
//weakPtr 就是用來保存指向這塊內存區域的指針的
//干了一大堆其他事情
boost::shared_ptr<test> sharePtr_2 = weakPtr.lock();
if (sharePtr_2)
sharePtr_2->print();
return ;
}
6. Boost::shared_array<T> 和Boost::scoped_array<T>
6.1 定义
前面提到过shared_ptr和scoped_ptr不能用于数组的内存(new []),所以shared_array和scoped_array就是他们的代替品。
6.2 例子
int _tmain(int argc, _TCHAR* argv[])
{
const int size = ;
boost::shared_array<test> a(new test[]);
for (int i = ; i < size; ++i)
a[i].print();
return ;
}
7.std::auto_ptr
7.1 定义
auto_ptr是C++标准库里的类,它接受一个类型形参的模板,为动态分配的对象提供异常安全。其实,它的核心思想是:用一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。
7.2 特点
(1)auto_ptr的构造函数带explicit 关键字,必须使用初始化的直接形式来创建auto_ptr对象。
auto_ptr<int> ap(new int()); //ok
auto_ptr<int> ap=new int(); //error
(2)auto_ptr 在析构函数中释放了动态分配的空间,因此能自动释放内存。下面函数只动态分配了内存,并没有显示释放。但是编译器保证在展开栈越过f之前运行pi的析构函数。
void f() { auto_ptr<int> ap(new int()); }
(3)auto_ptr重载了解引用操作符和箭头操作符,支持了普通指针的行为。
(4)赋值时删除了左操作数指向的对象
auto_ptr<int> ap1(new int()); auto_ptr<int> ap2;
ap2=ap1;
将ap1赋值给ap2后,删除了ap2原来指的对象;ap2置为指向ap1所指的对象;ap1为未绑定对象。可看代码。
(5)测试auto_ptr对象,可以调用get成员函数,该函数返回包含在auto_ptr对象中的基础指针。
if(ap.get()) *ap=; //ok
if(ap) *ap=; //error
7.3 使用原则
尽管auto_ptr类模板为处理动态分配的内存提供了安全性和便利性的尺度,但是也存在不少缺陷,接下来结合例子给出auto_ptr的一些缺陷。
(1)不要使用auto_ptr对象保存指向静态分配对象的指针。否则,当auto_ptr对象本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
int a=;
auto_ptr<int> ap(&a); //编译没有问题,会导致未定义行为
(2)不要使两个auto_ptr对象指向同一对象。
auto_ptr<int> ap1(new int ());
auto_ptr<int> ap2(ap1.get());
(3)不要使用auto_ptr对象保存指向动态分配数组的指针。从源代码中可以看出,它用的是delete操作符,而不是delete [ ] 操作符
(4)不要将auto_ptr对象存储在容器中。因为auto_ptr的复制和赋值具有破坏性。不满足容器要求:复制或赋值后,两个对象必须具有相同值。
7.4 例子
#include <utility>
#include <iostream>
using namespace std; class A
{
public:
A() { id = ++count; cout << "create A" << id << "\n"; }
~A() { cout << "destroy A" << id << "\n"; }
private:
static int count;
int id;
}; int A::count = ; /*调用该函数会丢失掉所有权*/
void sink(auto_ptr<A> a)
{
cout << "Enter sink()\n";
} /*调用该函数会创建对象,并获取所有权*/
auto_ptr<A> create()
{
cout << "Enter create()\n";
auto_ptr<A> a(new A());
return a;
} int main(int argc, char *argv[])
{
auto_ptr<A> a1 = create();
auto_ptr<A> a2 = a1; /*转移所有权,此时a1无效了*/
auto_ptr<A> a3(new A());
cout << "Exit create()\n";
sink(a2);/*丢失所有权,会发现a2的释放在sink函数中进行*/
cout << "Exit sink()\n";
return ;
} 输出结果是:<br>Enter create()<br>create A1<br>create A2<br>Exit create()<br>Enter sink()<br>destroy A1<br>Exit sink()<br>destroy A2<br><br>
8. 使用智能指针的几个注意点
(1)声明一个智能指针的时候要立即给它实例化, 而且一定不能手动释放它。
(2)…_ptr<T> 不是T* 类型。所以:
a: 声明的时候要…_ptr<T> 而不是….._ptr<T*>。
b:不能把T* 型的指针赋值给它。
c: 不能写ptr=NULL, 而用ptr.reset()代替。
(3)不能循环引用。
(4)不要声明临时的share_ptr, 然后把这个指针传递给一个函数。
原文链接:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html
Boost智能指针使用总结的更多相关文章
- 基于C/S架构的3D对战网络游戏C++框架_05搭建系统开发环境与Boost智能指针、内存池初步了解
本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...
- Boost智能指针-基础知识
简单介绍 内存管理一直是 C++ 一个比較繁琐的问题,而智能指针却能够非常好的解决问题,在初始化时就已经预定了删除.排解了后顾之忧.1998年修订的第一版C++标准仅仅提供了一种智能指针:std::a ...
- [转] Boost智能指针——scoped_ptr
http://www.cnblogs.com/tianfang/archive/2008/09/15/1291050.html boost::scoped_ptr和std::auto_ptr非常类似, ...
- boost 智能指针intrusive_ptr
boost::intrusive_ptr一种“侵入式”的引用计数指针,它实际并不提供引用计数功能,而是要求被存储的对象自己实现引用计数功能,并提供intrusive_ptr_add_ref和intru ...
- Boost智能指针——weak_ptr
循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象.一个简单的例子如下: #include <string>#include <iost ...
- boost智能指针总结
智能指针是一种具备指针类似行为的对象,当不在需要它的时候自动删除其引用的c++对象.直接点说就是自动析构C++对象. boost提供了6种智能指针,如下所示: scoped_ptr <boost ...
- Boost智能指针——scoped_ptr
boost::scoped_ptr和std::auto_ptr非常类似,是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放. 上一段代码,以及其输出: #include <string ...
- 智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...
- C++智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...
随机推荐
- Warning: skipping non-radio button in group. 的处理
1)把你的第一个Radio选中group属性: 2)把你“这个组中”的最后一个Raido控件“后”的一个控件(指得是控件添加先后的顺序,可以用ctrl+d来修改顺序)也选中group属性,如最后一个R ...
- erlang的格式化字符串
往pgsql里面写数据的时候,不能双引号,开始纠结的不行,用拼字符串的形式,后来发现可以格式化字符串,泪奔 data_format.erl -module(data_format). -export( ...
- 全排列函数C++实现
例题:求由123456789构成的所有九位数字 1 用C++的next_permutation函数 #include <iostream> #include <stdio.h> ...
- Account银行账户
package com.hanqi; //账户类 public class Account { String ZhangHao; double CunKuanYuE; Account(String Z ...
- python文本挖掘模版
import xlrd import jieba import sys import importlib import os #python内置的包,用于进行文件目录操作,我们将会用到os.listd ...
- [Java][Web]Response学习
// 在 http 中,meta 标签可以模拟响应头 response.setHeader("Content-type", "text/html;charset=UTF- ...
- 科普Spark,Spark是什么,如何使用Spark
科普Spark,Spark是什么,如何使用Spark 1.Spark基于什么算法的分布式计算(很简单) 2.Spark与MapReduce不同在什么地方 3.Spark为什么比Hadoop灵活 4.S ...
- 【学徒日记】Unity 动画调用事件
http://note.youdao.com/noteshare?id=a15f965fc57a0b25c87ee09388cf0f4a 具体内容看上面的链接. 1. 在脚本里写一个函数,它的参数只能 ...
- 第七章 HTTP流量管理(二) URL 重写
URL 重定向功能: 浏览器中输入 http://<host_name>:31380/v1/service-A/XXXX 经过下面的重定向,实际调用的是serviceA的/v1/XXXX ...
- FastJSON使用笔记
虽然使用FastJSON来生成JSON数据非常简单 最常用的的方法就是JSON.toJSONString()静态方法,这个方法参数可以给一个String,也可以是一个int,也可以给一个Object类 ...