10.5.4 Tss库(1)
我曾为上面提到的有关PTHREADS和Win32的TSS机制的4个问题大伤脑筋,最后我捋起袖子自己写了一个库,该库提供了我需要的功能。它包含8个函数和两个辅助类。其主函数与C和C++(www.cppentry.com)都是兼容的,如程序清单10.6所示:
程序清单10.6
- // MLTssStr.h - 函数被声明为 extern "C"
- int Tss_Init(void); /* Failed if < 0. */
- void Tss_Uninit(void);
- void Tss_ThreadAttach(void);
- void Tss_ThreadDetach(void);
- HTssKey Tss_CreateKey( void (*pfnClose)()
- , void (*pfnClientConnect)()
- , void (*pfnClientDisconnect)()
- , Boolean bCloseOnAssign);
- void Tss_CloseKey( HTssKey hEntry);
- void Tss_SetSlotValue( HTssKey hEntry
- , void *value
- , void **pPrevValue /* = NULL */);
- void *Tss_GetSlotValue(HTssKey hEntry);
跟所有的良好的API一样,它具有Init/Uninit1方法,以便确保在任何客户使用它之前得到正确的初始化。它还含有另外两个函数,用于attach(附加)到某个线程以及从某个线程detach(脱离),我们待会再来讨论它们。
其中4个函数采用了操纵键的习惯做法。然而,这些函数提供了额外的功能。Tss_CreateKey()的参数pfnClose接受一个可选的回调函数,以便提供线程终止时的清理能力,如果你不需要,那么传递NULL就行了。此外,如果你希望在槽位值被覆写的时候调用清理函数,就必须指定bCloseOnAssign参数为true。
pfnClientConnect和pfnClientDisconnect两个参数接受可选的回调函数,用于防止代码过早地消失。你可以以任何恰当的方式来实现这两个回调函数,只要能够确保pfnClose所指的函数在被需要时仍存在于内存中并可调用即可。我在使用该API的过程中曾遇到过这样的情况:为其他API指定Init/Uninit函数,或者去锁定和解锁一个位于内存中的动态库,或者必要时二者兼而有之。
Tss_CloseKey()和Tss_GetSlotValue()的语义跟你期望的一样。然而,Tss_SetSlotValue()跟它的PTHREADS/Win32版本相比具有一个额外的参数:pPrevValue,如果该参数是NULL的话,原先的值就会被覆盖掉,并且按照该键创建时要求的清理方式被清理。如果该参数不是NULL,任何清理操作都会被跳过,原先的值会被返回给调用者。这就允许对槽位值进行粒度更细的控制,同时在缺省情况下可提供强大的清理语义。
作为一个C API,下一步很自然是将它封装到域守卫类中去,我提供了两个。第一个是TssKey类,该类并不是特别引人注意,它只是用于简化接口,并且将RAII用于关闭键,因此我只展示其公共接口:
程序清单10.7
- template <typename T>
- class TssKey
- {
- public:
- TssKey( void (*pfnClose)(T )
- , void (*pfnClientConnect)()
- , void (*pfnClientDisconnect)()
- , Boolean bCloseOnAssign = true);
- ~TssKey();
- public:
- void SetSlotValue(T value, T *pPrevValue = NULL);
- T GetSlotValue() const;
- private:
- . . . // 成员,隐藏拷贝构造函数和赋值操作符
- };
其实现中包含了用于确保sizeof(T) == sizeof(void*)的静态断言(见1.4.7小节),这是为了防止任何想要将大对象按值保存到槽位中的错误企图。用户欲保存的值会被转型至参数化类型(即T),节省了你在客户代码中的工作。
另一个类更有趣一些。如果你使用槽位值的目的是为了创建单个实体然后去复用它,那么你通常应该遵循程序清单10.8中的模式:
程序清单10.8
- TssKey key_func(closeThing, . . .);
- . . .
- OneThing const &func(Another *another)
- {
- OneThing *thing = (OneThing*)key_func.GetSlotValue();
- if(NULL == thing)
- {
- thing = new OneThing(another);
- key_func.SetSlotValue(thing);
- }
- else
- {
- thing->Method(another);
- }
- return *thing;
- }
然而,如果该函数更复杂一些(大多数时候都是这样),那么其内部就会出现多处可能改变槽位值的地方。每一处都存在着资源泄漏的可能,这种资源泄漏是因在调用SetSlotValue()之前过早返回而导致的。出于这个原因,我提供了域守卫类TssSlotScope,见程序清单10.9。我得承认我对该类有些过分的偏爱,因为它是纯粹的RAII的体现。
程序清单10.9
- template <typename T>
- class TssSlotScope
- {
- public:
- TssSlotScope(HTssKey hKey, T &value)
- : m_hKey(hKey)
- , m_valueRef(value)
- , m_prevValue((value_type)Tss_GetSlotValue(m_hKey))
- {
- m_valueRef = m_prevValue;
- }
- TssSlotScope(TssKey<T> key, T &value);
- ~TssSlotScope()
- {
- if(m_valueRef != m_prevValue)
- {
- Tss_SetSlotValue(m_hKey, m_valueRef, NULL);
- }
- }
- private:
- TssKey m_key;
- T &m_valueRef;
- T const m_prevValue;
- // Not to be implemented
- private:
- . . . // 隐藏拷贝构造函数和赋值操作符
- };