一个人会一门语言不难,难的是会玩出花样,这就是技巧,当然玩的人多了这些技巧也就不怎么稀奇了,哈。
我就先说说一个叫“引用计数”的东西。顾名思义,就是记录一个对象被引用了几次。那么它的出发点是什么,这可能需要一个场景来描述。假设你有一个叫A的对象,在T1线程中被创建,之后可能会在T2线程或者T3线程中被使用。当然对象A是线程安全的,可以再任何线程中被安全使用,但问题是对象A该由谁来销毁?假如说T1和T2已经不需要使用对象A了,它们想留给T3线程来销毁,因为T3线程可能还在使用。这都是假设,反过来T3线程可能也不需要使用对象A了,那么它怎么知道T1和T2线程也不需要使用了呢?没错,引用计数该出现了。对象A在T1、T2和T3中被共享使用,那么它应该引用计数为3,当T1线程不需要使用对象A时对引用计数减1,这情况对T2和T3同样适用,只是在引用计数在变为0时就应该立马销毁对象A。很简单不是吗?实现的代码如下:
class SharedObjectT
{
public:
SharedObjectT(void) : m_ref(0)
{
}
protected:
virtual ~SharedObjectT(void) = 0
{
}
public:
Long AddRef(void)
{
return ++m_ref;
}
Long Release(void)
{
Long ref = --m_ref;
if (0 == ref)
{
delete this;
return 0;
}
return ref;
}
private:
t_Counter m_ref;
};
class SafeCounter
{
public:
SafeCounter(Long _c = 0) : m_c(_c)
{
}
Long operator ++ (void)
{
return ::InterlockedIncrement(&m_c);
}
Long operator -- (void)
{
return ::InterlockedDecrement(&m_c);
}
operator volatile const Long& (void) const volatile
{
return m_c;
}
operator volatile Long& (void) volatile
{
return m_c;
}
private:
Long m_c;
};
typedef SharedObjectT<SafeCounter> SharedObject;
SharedObject就是所谓的引用计数,只不过它叫共享对象,意味着只要从它这里派生的子类就是一个共享对象。细心的你可能会发现,SharedObjectT的析构函数被限制在“保护”,这似乎没什么意义,因为SharedObjectT本来就是抽象基类,生成不了实例。其实这里是要告诉派生类,最好也应该把析构函数进行“保护”。因为一个共享对象它不能存在于栈上,所以使用“保护”析构函数来达到禁止在栈上构建对象。这也是一个技巧。
我把递增递减单独实现为一个叫SafeCounter的类中,我觉得这样有更多的选择,比如你可以实现在Linux下的SafeCounter。
当赋予对象一个分身术时,管理它的引用计数成了一个麻烦,你不得不小心的增加一次引用和释放一次引用,以免破坏了对象引用次数。只要你够懒,就会想到一个方法。能不能让它也表现的像一个普通对象?我实在是不想看到AddRef和Release,看到都想吐。这时候“智能引用”该出现了。不要被名字迷惑,很难解释“智能”的含义。只有看代码才能体会:
class SharedObjectHandlerT
{
public:
SharedObjectHandlerT(t_Type* _obj = Null)
: m_obj(_obj)
{
if (m_obj)
m_obj->AddRef();
}
SharedObjectHandlerT(const SharedObjectHandlerT& _rhs)
: m_obj(_rhs.m_obj)
{
if (m_obj)
m_obj->AddRef();
}
virtual ~SharedObjectHandlerT(void)
{
Safe_Release(m_obj);
}
operator void* (void) const
{
return m_obj;
}
t_Type* operator -> (void)
{
return m_obj;
}
const t_Type* operator -> (void) const
{
return m_obj;
}
t_Type& operator * (void)
{
return *m_obj;
}
const t_Type& operator * (void) const
{
return *m_obj;
}
bool operator == (const SharedObjectHandlerT& _rhs) const
{
return m_obj == _rhs.m_obj;
}
bool operator != (const SharedObjectHandlerT& _rhs) const
{
return m_obj != _rhs.m_obj;
}
bool operator == (const t_Type* _obj) const
{
return m_obj == _obj;
}
bool operator != (const t_Type* _obj) const
{
return m_obj != _obj;
}
SharedObjectHandlerT& operator = (SharedObjectHandlerT _rhs)
{
if (m_obj == _rhs.m_obj)
return *this;
Safe_Release(m_obj);
if (_rhs.m_obj)
{
_rhs.m_obj->AddRef();
m_obj = _rhs.m_obj;
}
return *this;
}
SharedObjectHandlerT& operator = (t_Type* _obj)
{
if (m_obj == _obj)
return *this;
Safe_Release(m_obj);
if (_obj)
{
_obj->AddRef();
m_obj = _obj;
}
return *this;
}
private:
t_Type* m_obj;
};
SharedObjectHandlerT的模板参数必须传入SharedObject类型或者兼容它的类型。这就是所谓的“智能引用”,它在构造函数中增加引用,在析构函数中释放引用,利用C++对象的这一个特性实现自动的引用计数功能。实际上它没看上去那么简单,看到它那个赋值函数吗?它的参数不是引用(&)参数,是传值的,之所以这样设计是有一个复杂的理由,这个理由会绕晕你的脑袋。假设有一个智能引用M,我们知道它内部有一个m_obj是共享对象,假设这个共享对象内部也包含了一个智能引用N(M和N是相同类型的SharedObjectHanderT实例),如果出现下面语句会怎么样?
是不是感觉到菊花一紧?根据SharedObjectHanderT的赋值函数的实现,它需要先释放内部m_obj的一次引用,而正是因为要释放引用,假如这次释放引用正好达到销毁m_obj的条件,那么在m_obj中的N还能存活吗?因此,需要传值,对N进行分身,即使m_obj被销毁,N的分身还存在于_rhs中,如此才能成功赋值!
SharedObjectHanderT在多线程中不能被引用传递给多个线程使用,这不是它的能力,但是可以成为某个共享对象的数据成员。在多线程穿梭中,可以把这个数据成员拷贝给任何线程,除非保证线程安全否则不要重新设置这个数据成员。
对多线程编程的一点经验:线程中的临时对象是绝对线程安全的,因为它是这个线程堆栈中的对象。
给个问题:两个不同的SharedObjectHanderT对象X和Y,m_obj都指向同一个共享对象,在多线程中如果X和Y都被赋值新的值(调用赋值函数),那么m_obj安全吗?最后他们的值如何?