loki游戏:智能指针的标准的争:Boost vs. Loki



   2001 年10 月和2002 年4 月在美国华盛顿和荷兰列斯群岛上分别召开了两次C标准会议会议内容的是对项新C特性提议——智能指针(Smart Poer)——进行讨论本文将对可能成为C新标准两种智能指针方案(Boost vs. Loki)进行介绍和分析并给出了相应使用例子

关键词:智能指针 C Boost Loki

   在现在标准C只有种智能指针:std::auto_ptr其原因并非是auto_ptr 已足以应付所有相关工作——实际上auto_ptr 有个重大缺陷就是它不能被用在STL 容器中——而是现在C标准在制定时并未能对智能指针进行全面考察按照C标准委员会成员Herb Sutter 说法只有种标准智能指针是件“可羞”事情:首先智能指针所能做许多有用事情是可怜auto_ptr 不能完成;其次在有些情况下使用auto_ptr 可能会造成问题上面所说不能在容器中使用就是实际上许多员已经开发了各种有用智能指针有些甚至在auto_ptr 被定为标准的前就已存在但问题是它们不是标准在这样情况下C标准委员会考虑引入新智能指针也就是自然而然事情了目前进入委员会视野主要有两种智能指针方案:Boost 智能指针和Loki 智能指针前者是由C标准委员会库工作组发起Boost 组织开发而后者由世界级C专家Andrei Alexandrescu 开发并在他所著“Modern C Design”书中进行了详细阐释下面让我们分别来看看这两种方案各自技术特点


、 Boost 智能指针
Boost 智能指针方案实现了 5种智能指针模板类每种智能指针都用于区别这 5种智能指针是:

   template scoped_ptr;
   template scoped_.gif' />;
   template shared_ptr;
   template shared_.gif' />;
   template weak_ptr;

下面将分别介绍它们各自特性并给出相应使用例子:


scoped_ptr :意在用作指向自动(栈)对象、不可复制智能指针该模板类存储是指向动态分配对象(通过 分配)指针被指向对象保证会被删除或是在scoped_ptr 析构时或是通过显式地re 思路方法注意该模板没有“共享所有权”或是“所有权转让”语义同时它也是不可复制(noncopyable)如此在用于不应被复制指针时它比shared_ptr 或std:auto_ptr 要更安全和auto_ptrscoped_ptr 也不能用于STL 容器中;要满足这样需求应该使用shared_ptr另外它也不能用于存储指向动态分配指针这样情况应使用scoped_.gif' />

下面是使用scoped_ptr 个简单例子:

CTest
{
public:
CTest : m_id(0) {}
CTest( id) : m_id(id) {}
~CTest { std::cout << \"id: \" << m_id << \" - Destructor is being called\\n\"; }
void SetId( id) { m_id = id; }
GetId { m_id; }
void DoSomething
{ std::cout << \"id: \" << m_id << \" - Doing something\\n\"; }
private:
m_id;
};
void
{
boost::scoped_ptr pTest( CTest);
pTest->DoSomething;
}

其运行结果为:
   id: 0 - Doing something
   id: 0 - Destructor is being called
(以下几个例子所用CTest 类定义完全相同为节省篇幅不再列出——作者)

显然尽管我们自己没有deletepTest 仍然为我们正确地删除了它所指向对象看起来scoped_ptr用途和auto_ptr 十分类似但实际上scoped_ptr 类型指针所有权不可转让点是和auto_ptr相当区别


scoped_.gif' /> :该模板类和scoped_ptr 类似但意在用于而不是单个对象std::vector 可用于替换scoped_.gif' />并且远为灵活但其效率要低在不使用动态分配时boost::.gif' /> 也可用于替换scoped_.gif' />

下面是个使用scoped_.gif' /> 例子:

void
{
boost::scoped_.gif' /> pTest( CTest[2]);
pTest[0].SetId(0);
pTest[1].SetId(1);
pTest[0].DoSomething;
pTest[1].DoSomething;
std::cout << \'\\n\';
}

其运行结果为:
   id: 0 - Doing something
   id: 1 - Doing something
   id: 1 - Destructor is being called
   id: 0 - Destructor is being called
scoped_.gif' /> 将负责使用delete 而不是delete 来删除它所指向对象


shared_ptr :意在用于对被指向对象所有权进行共享和scoped_ptr 被指向对象也保证会被删除但区别这将发生在最后个指向它shared_ptr 被销毁时或是re 思路方法时shared_ptr符合C标准库“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求所以可被用于标
库容器中另外它还提供了比较操作符所以可和标准库关联容器起工作shared_ptr 不能用于存储指向动态分配指针这样情况应该使用shared_.gif' />该模板实现采用了引用计数技术所以无法正确处理循环引用情况可以使用weak_ptr 来“打破循环”shared_ptr 还可在多线程环境中使用

下面例子演示怎样将shared_ptr 用于std::vector 中:

typedef boost::shared_ptr TestPtr;
void PT(const TestPtr &t)
{
std::cout << \"id: \" << t->GetId << \"\\t\\t\" << \"use count: \" << t.use_count << \'\\n\';
}
void
{
std::vector TestVector;
TestPtr pTest0( CTest(0));
TestVector.push_back(pTest0);
TestPtr pTest1( CTest(1));
TestVector.push_back(pTest1);
TestPtr pTest2( CTest(2));
TestVector.push_back(pTest2);
std::for_each(TestVector.begin, TestVector.end, PT);
std::cout <

< \'\\n\';
pTest0.re;
pTest1.re;
pTest2.re;
std::for_each(TestVector.begin, TestVector.end, PT);
std::cout << \'\\n\';
TestVector.clear;
std::cout << \'\\n\';
std::cout << \"exiting...\\n\";
}

其运行结果为:
   id: 0 use count: 2
   id: 1 use count: 2
   id: 2 use count: 2
   id: 0 use count: 1
   id: 1 use count: 1
   id: 2 use count: 1
   id: 0 - Destructor is being called
   id: 1 - Destructor is being called
   id: 2 - Destructor is being called
   exiting...

运行结果中“use count”是通过shared_ptr use_count思路方法获得“使用计数”也就是对所存储指针进行共享shared_ptr 对象数目我们可以看到在通过 分配了3 个CTest 对象并将相应shared_ptr 对象放入TestVector 后 3个使用计数都为2;而在我们使用re思路方法复位pTest0、pTest1 和pTest2 后TestVector 中各个shared_ptr 对象使用计数变成了1这时我们TestVectorclear思路方法清除它所包含shared_ptr 对象;已经没有shared_ptr 对象再指向我们先前分配3个CTest 对象这3 个对象也随的被删除并导致相应析构器被


shared_.gif' /> :该模板类和shared_ptr 类似但意在用于而不是单个对象指向std::vector shared_ptr 可用于替换scoped_.gif' />并且远为灵活但其效率也要低

下面是使用例子:

void
{
boost::shared_.gif' /> pTest1( CTest[2]);
pTest1[0].SetId(0);
pTest1[1].SetId(1);
std::cout << \"use count: \" << pTest1.use_count << \"\\n\\n\";
boost::shared_.gif' /> pTest2(pTest1);
std::cout << \"use count: \" << pTest1.use_count << \"\\n\\n\";
pTest1.re;
pTest2[0].DoSomething;
pTest2[1].DoSomething;
std::cout << \'\\n\';
std::cout << \"use count: \" << pTest1.use_count << \"\\n\\n\";
}

其运行结果为:
   use count: 1
   use count: 2
   id: 0 - Doing something
   id: 1 - Doing something
   use count: 1
   id: 1 - Destructor is being called
   id: 0 - Destructor is being called
如此例所示我们通过 所分配只有在指向它pTest1 和pTest2 都被销毁或复位后才被删除


weak_ptr :该模板类存储“已由shared_ptr 管理对象”“弱引用”要访问weak_ptr 所指向对象可以使用shared_ptr 构造器或make_shared 来将weak_ptr 转换为shared_ptr指向被管理对象最后个shared_ptr 被销毁时将删除该对象即使仍有weak_ptr 指向它也是如此和原始指针区别届时最后个shared_ptr 会检查是否有weak_ptr 指向该对象如果有话就将这些weak_ptr 置为空这样就不会发生使用原始指针时可能出现“悬吊指针”(dangling poer)情况从而获得更高安全水平
   weak_ptr 符合C标准库“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求所以可被用于标准库容器中另外它还提供了比较操作符所以可和标准库关联容器起工作

void
{
boost::shared_ptr pTest( CTest);
boost::weak_ptr pTest2(pTest);
(boost::shared_ptr pTest3 = boost::make_shared(pTest2))
pTest3->DoSomething;
pTest.re;
assert(pTest2.get NULL);
}

其运行结果为:
   id: 0 - Doing something
   id: 0 - Destructor is being called
最后断言确认了pTest2 所存储指针确已被置为NULL

   显然Boost 智能指针方案会让我们产生这样疑问:如果我们还需要其他类型智能指针(比如支持COM 智能指针)是否意味着我们必须在C中再增加智能指针类型或是采用非标准实现呢?在泛型技术已得到极大发展今天Boost “增加增加再增加”思路是不能让人满意正是在这里我们看到了下面将要介绍Loki Smart Poer 关键点:通过基于策略(policy-based)设计来实现通用智能指针模板


2、 Loki 智能指针
   按照美国传统辞典(双解)解释Loki 是“A Norse god who created discord, especially among his fellow gods.”(斯堪纳维亚个制造混乱尤其是在其同类的间)就其给Boost 智能指针带来麻烦而言Loki 智能指针倒真当得起这个名字;而在另方面就其实现优雅以及功能强大(也就是说它给开发者带来好处)而言它也确属于“神族”
   上面已经说过Loki 智能指针方案采用了基于策略设计其要点在于把将各功能域分解为独立、由主模板类进行混合和搭配策略让我们先来看看Loki 智能指针模板类SmartPtr 定义:

template
<
typename T,
template <> OwnershipPolicy =RefCounted,
ConversionPolicy =DisallowConversion,
template <> CheckingPolicy =AssertCheck,
template <> StoragePolicy =DefaultSPStorage
>
SmartPtr;

我们可以看到除了SmartPtr 所指向对象类型T 以外在模板类SmartPtr 中包括了这样些策略:OwnershipPolicy(所有权策略)、ConversionPolicy(类型转换策略)、CheckingPolicy(检查策略)、StoragePolicy(存储策略)正是通过这样分解使得SmartPtr 具备了极大灵活性我们可以任意组合各种区别策略从而获得区别智能指针实现下面先对各个策略逐进行介绍:

OwnershipPolicy :指定所有权管理策略可以从以下预定义策略中选择:DeepCopy(深度复制)、RefCounted(引用计数)、RefCountedMT(多线程化引用计数)、COMRefCounted(COM 引用计数)、RefLinked(引用链接)、DestructiveCopy(销毁式复制)以及NoCopy(无复制)


ConversionPolicy :指定是否允许进行向被指向类型隐式转换可以使用实现有AllowConversion 和DisallowConversion
CheckingPolicy :定义检查策略可以使用AssertCheck、AssertCheckStrict、RejectNullStatic、RejectNull、RejectNullStrict以及NoCheck
StoragePolicy :定义怎样存储和访问被指向对象Loki 已定义策略有:DefaultSPStorage、ArrayStorage、LockedStorage以及HeapStorage

除了Loki 已经定义策略你还可以自行定义策略实际上Loki 智能指针模板覆盖了 4种基本Boost 智能指针类型:scoped_ptr、scoped_.gif' />、shared_ptr 和shared_.gif' />;至于weak_ptr也可以通过定义相应策略来实现其等价物通过即将成为C标准(C0x)typedef 模板特性我们还可以利用Loki SmartPtr 模板来直接定义前面提到Boost 前 4种智能指针类型举例来说我们可以这样定义:

shared_ptr:
template // typedef 模板还不是标准
typedef Loki::SmartPtr
<
T,
RefCounted, // 以下都是缺省模板参数
DisallowConversion,
AssertCheck,
DefaultSPStorage
>
shared_ptr;

下面是个使用Loki “shared_ptr”例子:

typedef Loki::SmartPtr TestPtr;
void PT(const TestPtr &t)
{
std::cout << \"id: \" << t->GetId << \'\\n\';
}

void
{
std::vector TestVector;
TestPtr pTest0( CTest(0));
TestVector.push_back(pTest0);
TestPtr pTest1( CTest(1));
TestVector.push_back(pTest1);
std::for_each(TestVector.begin, TestVector.end, PT);
std::cout << \'\\n\';
Loki::Re(pTest0, NULL);
Loki::Re(pTest1, NULL);
std::for_each(TestVector.begin, TestVector.end, PT);
std::cout << \'\\n\';
TestVector.clear;
std::cout << \'\\n\';
std::cout << \"exiting...\\n\";
}

其运行结果为:
   id: 0
   id: 1
   id: 0
   id: 1
   id: 0 - Destructor is being called
   id: 1 - Destructor is being called
   exiting...

前面已经提到要通过Loki 定义和Boost shared_ptr 功能等价智能指针除了第个模板参数以外其他参数都可以使用缺省值所以在上面例子中我们直接使用“typedef Loki::SmartPtr TestPtr;”就可以了非常简单!

为了进步介绍说明Loki “基于策略设计思路方法”让我们再来看个更为复杂例子:通过Loki::SmartPtr 实现线程专有存储(Thread-Specic StorageTSS;又称线程局部存储Thread Local StorageTLS)

所谓线程专有存储是指这样种机制通过它多线程可以使用个逻辑上全局访问点来访问线程专有数据并且不会给每次访问增加额外锁定开销个简单例子在C 语言中我们可以通过errno变量来获取代码;通常errno 就是个普通全局变量——在单线程环境中这当然没有什么问题但如果
是多线程环境这个全局errno 变量就会给我们带来麻烦了TSS 正是解决这问题有效方案

显然智能指针语义能够很好地适用于TSS我们可以编写种智能指针使得所有对其所指向对象访问都成为线程专有——也就是说每个线程访问实际上是自己专有对象但从外表来看却都是对同对象访问有了Loki::SmartPtr我们可以非常容易地实现这样智能指针:如其名字所指示TSS 涉及是存储问题我们只要对Loki::SmartPtr StoragePolicy 进行定制就可以了其他工作可以交给Loki::SmartPtr 去完成

在POSIX PThreads 库和Win32 中都提供了用于线程专有存储它们分别是pthread_key_create、pthread_specic、pthread_getspecic 和pthread_key_delete(POSIX PThreads 库)以及TlsAlloc、TlsSetvalue、TlsGetvalue 和TlsFree(Win32)有关这些详细信息请参阅相关文档

下面给出在MSVC 6.0 下实现用于TSS StoragePolicy并通过注释逐行进行分析(这个实现使用了PThreads-Win32 库这是个Win32 上PThreads 实现使用Win32 线程专有也可以实现类似StoragePolicy但编写在线程退出时CleanupHook却需要点“窍门”具体思路方法可参考Boost thread_specic_ptr 实现):

Loki
{
// 实现TSS Loki 存储策略改编自Douglas C. Schmidt、Timothy H. Harrison
// 和Nat Pryce 论文“Thread-Specic Storage for C/C”中部分代码
// 使用了“Loki VC 6.0 Port”
template < T> TS_SPStorage
{
public:
typedef T* StoredType; // 被指向对象类型
typedef T* PoerType; // operator->所返回类型
typedef T& ReferenceType; // operator*所返回类型
public:
// 构造器对成员变量进行
TS_SPStorage : _disibledevent=>


PoerType tss_data = NULL;
// 使用双重检查锁定模式来在除了化以外情况下避免锁定
// 的所以在这里而不是在构造器中对“专有钥”进行化及分配TS 对象
// 是:(1) 最初创建TS 对象线程(例如主线程)常常并不是使用
// 它线程(工作线程)所以在构造器中分配个TS 对象例子常常并无
// 好处这个例子只能在主线程中访问(2)在有些平台上“专有
// 钥”是有限资源所以等到对TS 对象进行第次访问时再进行分配
// 助于节省资源
// 第次检查
(once_ 0)
{
// 加锁确保访问序列化
pthread_mutex_lock(&keylock_);
// 双重检查
(once_ 0)
{
// 创建“专有钥”
pthread_key_create(&key_, &CleanupHook);
// 必须在创建过程最后出现这样才能防止其他线程在“专有钥”
// 被创建的前使用它
once_ = 1;
}
// 解锁
pthread_mutex_unlock(&keylock_);
}
// 从系统线程专有存储中获取数据注意这里不需要加锁
tss_data = (PoerType)pthread_getspecic(key_);
// 检查是否这是当前线程第次进行访问
(tss_data NULL)
{
// 从堆中为TS 对象分配内存
tss_data = T;
// 将其指针存储在系统线程专有存储中
pthread_specic(key_, (void *)tss_data);
}
tss_data;
}
private:
// 用于线程专有数据“专有钥”
pthread_key_t key_;
// “第次进入”标志
_disibledevent=>void *thread_proc(void *param)
{
id = ()param;
*value = 0;
for ( i = 0; i < 3; i)
{
(*value);
pthread_mutex_lock(&io_mutex);
std::cout << \"thread \" << id << \": \" << *value << \'\\n\';
pthread_mutex_unlock(&io_mutex);
}
NULL;
}

void ( argc, char* argv)
{
pthread_mutex_init(&io_mutex, NULL);
pthread_t id[3];
for( i = 0; i < 3; i)
pthread_create(&id[i], 0, thread_proc, (void *)i);
for( i = 0; i < 3; i)
pthread_join(id[i], NULL);
pthread_mutex_destroy(&io_mutex);
}

其运行结果为:
   thread 0: 1
   thread 0: 2
   thread 1: 1
   thread 2: 1
   thread 1: 2
   thread 2: 2
   thread 1: 3
   thread 2: 3
   thread 0: 3

由此我们可以看出尽管看起来在各个线程中访问都是同样*value但实际上访问却是各自线程专有对象而且除了化以外对这些对象访问无需进行序列化Loki::SmartPtr 为我们做了大量工作TS_SPStorage 实现十分简洁明了有兴趣读者可以对这里实现和Boost thread_specic_ptr 进行比较像这样对Loki::SmartPtr 策略进行定制在理论上数目是无限也就是说通过它我们可以拥有 5花 8门、“千奇百怪”智能指针——了不起Loki不是吗?有了这样模板类我们就再不需要次地为标准C增加新智能指针类型了


3、 结束语
   在推进器Boost 和斯堪纳维亚的神Loki 战争中谁将胜出恐怕已不言而喻倘若扮演上帝C标准委员会起了偏心硬要选中Boost 智能指针方案那么在接下来日子里他们就将不再是C标准委员会而是C智能指针救火委员会了而即便那样Loki斯堪纳维亚的神也仍将矗立在C大陆上为他“子民”——C员们——带来统、秩序和和谐

相关资源
1. Herb SutterThe New C: Smart(er) Poers见C User Journal 网站WebSite:http://www.cuj.com/experts/2008/sutter.htm
2. Boost 文档:http://www.boost.org
3. Andrei AlexandrescuModern C Design 及Loki见http://www.moderncppdesign.com或http://www.awprofessional.com/catalog/product.asp?product_id=%7B4ED3E6F3-371F-4ADC-9810-CC7B936164E3%7D
4. Douglas C. Schmidt、Timothy H. Harrison 和Nat PryceThread-Specic Storage for C/C见http://www.cs.wustl.edu/~schmidt/或http://www.flyingdonkey.com/ace/(中文)
5. Herb SutterTemplate Typedef见http://www.gotw.ca/gotw/079.htm
6. PThreads-Win32 库见http://sources.redhat.com/pthreads-win32/

Tags:  loki版本 lokiloki loki修改器 loki游戏

延伸阅读

最新评论

发表评论