可移植的虚函数表
C++并非一门合适的模块——接口语言,为了支持ABI/OAB(Application Binary Interface/Objects Across Borders)需要付出相当程度的努力,并且使用者还必须受到严格的约束。下面介绍一种得到广泛运用的跨边界对象技术,如果你熟悉Win32 COM,肯定立马心领神会。
接口代码:
// Interface.h
//
struct IObject;
struct IObjectVTable
{
void (*SetName)(struct IObject * obj, char const * s);
char const * (*GetName)(struct IObject const * obj);
};
struct IObject
{
struct IObjectVTable * const vtable;
#ifdef __cplusplus
protected:
IObject(struct IObjectVTable * vt)
: vtable(vt)
{}
~IObject()
{}
public:
inline void SetName(char const * s)
{
assert(NULL != vtable);
vtable->SetName(this, s);
}
inline char const * GetName() const
{
assert(NULL != vtable);
return vtable->GetName(this);
}
private:
IObject(IObject const & rhs);
IObject & operator=(IObject const & rhs);
#endif /* __cplusplus */
};
extern "C" IObject * createObject();
extern "C" void destroyObject(IObject *);
服务端代码:
// Server.cpp
//
class Object : public IObject
{
public:
virtual void SetName(char const * s)
{
m_name = s;
}
virtual char const * GetName() const
{
return m_name.c_str();
}
#ifndef POAB_COMPILER_HAS_COMPATIBLE_VTABLES
public:
Object()
: IObject(GetVTable())
{}
Object(Object const & rhs)
: IObject(GetVTable())
, m_name(rhs.m_name)
{}
private:
static void SetName_(IObject * this_, char const * s)
{
static_cast<Object*>(this_)->SetName(s);
}
static char const * GetName_(IObject const * this_)
{
return static_cast<Object const *>(this_)->GetName();
}
static IObjectVTable * GetVTable()
{
static IObjectVTable s_vt = MakeVTable();
return &s_vt;
}
static IObjectVTable MakeVTable()
{
IObjectVTable vt = { SetName_, GetName_ };
return vt;
}
#endif /* !POAB_COMPILER_HAS_COMPATIBLE_VTABLES */
private:
std::string m_name;
};
客户端代码:
// Client.cpp
//
int _tmain(int argc, _TCHAR* argv[])
{
IObject * pObj = createObject();
pObj->SetName("Matthew Wilson");
std::cout << "name: " << pObj->GetName() << std::endl;
destroyObject(pObj);
return 0;
}
synchronized关键字
Java中有synchronized关键字,.NET中有lock关键字,它们可以被用于守护临界区。而C++标准对线程几乎只字未提,自然也没有相关设施。而然借助一点点宏,我们就能轻松实现同样的效果:(lock_scope请参考笔记(一)中的域守卫类)
#define SYNCHRONIZED_BEGIN(T, v) \
{ \
lock_scope<T> __lock__(v);
#define SYNCHROIZED_END() \
}
如果你和我一样不喜欢SYNCHROIZE_END()部分,毕竟和synchronized或lock比起来似乎不那么优雅,那么可以用点小技巧,像这样定义宏:
#define synchronized(T, v) \
for (synchronized_lock<lock_scope<T> > __lock__(v); \
__lock__; __lock__.end_loop())
template<typename T>
struct synchronized_lock : public T
{
public:
template<typename U>
synchronized_lock(U & u)
: T(u)
, m_bEnded(false)
{}
operator bool () const
{
return !m_bEnded;
}
void end_loop()
{
m_bEnded = true;
}
private:
bool m_bEnded;
};
客户代码:
synchronized(Object, obj)
{
... // 临界区代码
}
... // 非临界区代码
synchronized(Object, obj)
{
... // 临界区代码
}
上述代码,熟悉JAVA的朋友是不是很亲切?不过还是有个小小的遗憾,那就是对象的类型不能被自动推导出来,导致我们必须手工提供两个参数。
这里还有个问题要注意,如果同一个作用域中出现了两个同步段,某些老式编译器(如大名鼎鼎的VC6.0)对for循环处理的不那么“标准”,就会报出“重新定义__lock__”的错误。所以作者对此提供了一个可移植的解决方案,来确保每个“__lock__”都是不一样的:
#define concat__(x, y) x ## y
#define concat_(x, y) concat__(x, y) // 用两层宏来取__LINE__的实际值
#define synchronized(T, v) \
for (synchronized_lock<lock_scope<T> > concat_(__lock__,__LINE__)(v); \
concat_(__lock__,__LINE__); concat_(__lock__,__LINE__).end_loop())
但是,作者似乎忘了,某些老式编译器(如VC6.0)对循环变量的释放,会延迟到整个作用域结束时,而不是临界代码段的右括号处。所以该解决方案是错误的!
局部静态对象
函数局部静态对象的初始化存在的固有的竞争条件是非常重要的问题。因此不能被忽略。然而,它同时也是非常罕见的情况。我们可以利用其罕见性,给出一个极其优雅的解决方案,该解决方案基于自旋互斥体。自旋互斥体本身依赖于原子操作,它的开销是非常低的。
class spin_mutex
{
public:
explicit spin_mutex(int * p = NULL)
: m_spinCount((NULL != p) ? p : &m_internalCount)
, m_internalCount(0)
{}
void lock()
{
for (; 0 != atomic_write(m_spinCount, 1); sched_yield())
{} // PTHREAD函数seched_yield()将执行机会让给其他线程
}
void unlock()
{
atomic_set(m_spinCount, 0);
}
private:
int * m_spinCount;
int m_internalCount;
private:
spin_mutex(spin_mutex const & rhs);
spin_mutex & operator=(spin_mutex const & rhs);
};
Local & GetLocal()
{
static int guard; // 在加载期被初始化为0
spin_mutex smx(&guard); // 对guard进行自旋
lock_scope<spin_mutex> lock(smx); // 使用smx来加锁
static Local local;
return local;
}