本文开发一个简单适用的日志类Log,讲述C++一些比较好玩的特性,template、策略类、多线程锁、单件、函数可变参数等的方法。涉及的东西比较多,可能一篇写不完。
在开始Log类之前,先讲解几个简单的class,这些class往往比较小,完成单一的功能,利用C++多重继承机制,把这些小类拼装成比较复杂的类。这些小class,称为策略类。枯燥无味的概念不是重点,《C++设计新思维》的第一章有详细讲述。我只show代码,用代码说明。
第一个类是NoCopy,字面意思就是让类不能复制,具体的就是不能通过构造函数和赋值函数来构造对象。要达到这个目的,只需要自定义类型从这个类派生即可。原理就是把拷贝构造函数和赋值函数屏蔽掉,叫编译器编译不过。代码如下:
class NoCopy
{
NoCopy( const NoCopy& );//私有
NoCopy& operator=( const NoCopy & ); //私有
public:
NoCopy(void){}//提供缺省的构造函数
};
这里明确定义缺省构造函数,原因是声明了私有的拷贝构造函数和赋值函数,结果缺省构造函数就没了,派生类会出现编译错误,报告NoCopy无缺省构造函数。
第二类是一组类,跟多线程有关。多线程环境下,考虑到并发情况,数据的访问要保证互斥,不能破坏掉。一些伪码如下:
class Something
{
public:
bool WriteOp(...)
{
m_lock.Lock();
...
m_lock.Unlock();
}
private:
XxxLock m_lock;//互斥对象
};
为了实现跨平台,所使用的互斥对象,不能依赖具体某个平台,比如win32,而且要求这种加锁机制,由使用者自己决定是否采用。互斥的时候,需要成对使用Lock和Unlock,防止忘记解锁,导致死锁。一个惯用法比较常用,就是利用构造函数和析构函数。首先来看门卫锁,也叫范围锁的类型:
template<typename LockT>
class GuardLock
{
public:
GuardLock( const LockT &lock)
:m_lock( const_cast<LockT&>(lock) )
{
m_lock.Lock();
}
~GuardLock(void)
{
m_lock.Unlock();
}
private:
LockT &m_lock;
};
GuardLock类型并未对所使用的互斥对象进行限制,通过模板参数来指定。这样可以方便实现跨平台。使用方法如下:
CCriticalSection m_cs;//win32的关键代码段
GuardLock<CriticalSection> lock;
关于锁的类型,在《C++设计新思维》一书中说了主要有三种:
1、空锁,也就是单线程,Lock和Unlock是空操作;
2、对象锁,就是每个对象都有一个互斥对象,加锁针对单个对象;
3、类锁,就是每个类有一个互斥对象,加锁针对类;
三个不同的锁类型,我们可以通过三个小的class来实现,类型只要从这些小class派生,就可以具备不同的加锁保护。首先来看一下空锁的类型定义:
template<class Host>
class EmptyLock
{
public:
struct Lock
{
Lock() {}
explicit Lock( const EmptyLock& ) {}//子类是父类的关系是:is a
};
};
上述的模板Host,是指所要定义的派生类。具体的用法,后面再介绍。还有两个类型的锁,定义为:
template<typename Host, typename LockT>
class ObjectLock
{
LockT m_lock;//普通成员
public:
ObjectLock()
:m_lock()
{
}
~ObjectLock()
{
}
class Lock;
friend class Lock;
class Lock : NoCopy//嵌套类
{
ObjectLock& m_host;
public:
explicit Lock(const ObjectLock& host)
: m_host(const_cast<ObjectLock>(host))
{
m_host.Lock();
}
~Lock(void)
{
m_host.Unlock();
}
};
};
template <typename Host, typename LockT>
class ClassLock
{
static LockT s_classLock;//静态成员
public:
class Lock;
friend class Lock;
class Lock : NoCopy
{
public:
explicit Lock( const ClassLock& )
{
ClassLock::s_classLock.Lock();
}
~Lock(void)
{
ClassLock::s_classLock.Unlock();
}
};
};
template <typename Host, typename LockT>
LockT ClassLock<Host, LockT>::s_classLock;//定义静态成员变量
对象锁和类锁的不同地方,就是互斥对象,一个是普通成员变量,一个是静态成员变量,大家都知道,静态成员变量是属于类的,不属于对象。关于对象和类的区别,没有搞明白的,可能需要加深理解。简单的举一个例子,比如狗,狗是类,一只叫小白的狗则是一个对象。现在设计一个类,具备对象锁的互斥能力。代码如下:
class MyClass : public ObjectLock<MyClass, CCriticalSection>
{
public:
void TestLock(void)
{
Lock lock(*this);//这里就是调用具体的加锁函数
//other code;
}
};
这里使用win32的关键代码段举例子,使得MyClass就不是模板类了。现在MyClass类就具备了对象锁的特性。如果后续需要改成类锁,就只需要改一下类型的声明即可:
class MyClass : public ClassLock<MyClass, CCriticalSection>
{
};
现在,也许大家已经初步见到这些小class(策略类)的作用,通过简单的组建,就可以使得类型具备一些复杂的特性,而改动地方很小。对于一个严肃的开发人员来说,写超过100行的函数,一般会比较小心谨慎的。同样,对于一个类型来说,同样是如此,把一个类型设计成小儿简单的,也是代码设计的基本功,C++远远不是带class的C。
说了这么多,还只是Log类一些准备工作,下一篇再来讲述。
来源:KiteRunner