这里我会先举两个小例子阐述预处理器宏中实现变参的方法,然后结合一个临界类型模版类TCriticalType的简单例子讲述我在实际应用中如何用到变参宏的。
在C++ 中按ANSI标准变参函数的定义语法是这样的:
//typedef int SomeDataType1;
//typedef int SomeDataType2;
SomeDataType2 TestProc( SomeDataType1 first, … );
或
SomeDataType1 TestProc( … );
也就是用三个连续的 . 符号表示参数表。
而带参的宏定义语法如下:
#define TESTPROC(x)
或
#define TESTPROC(x1,x2)
等
总之如上,宏里面是不允许有像变参函数那样的参数表的定义语法的,那如何让宏具有变参的功能呢?宏其实就是一段代码,事实上在预处理器中就是用宏代码替代宏调用的,反应快的话,这里你就可以看出了——其实不太准确地,宏也就可以看成是一段脚本。脚本语言都有一个共通之处,那就是都提供字符引用之类的功能,在C++ 的宏中用#指示字符串的语法就类似于此——见如下例子:
#include <iostream>
using namespace std;
#define TEST(x) cout << #x" = " << x << endl
int main( int argc, char* argv[] )
{
int a = 12;
TEST(a);
return 0;
}
运行结果,屏幕输出:
a = 12
再看一个稍复杂的例子(这里我们先约定以下代码是路径名为c:\test.cpp的文件里的完整内容):
#include <iostream>
#define __MY_STR(x) #x
#define __MY_STR2(x) __MY_STR(x)
#define MY_FILE_LINE __FILE__ "(" __MY_STR2(__LINE__) "):"
#define prompt(desc) message(MY_FILE_LINE desc)
int main( int argc, char* argv[] )
{
#pragma prompt(“incomplete here…”) // 第8行
return 0;
}
编译器的编译输出显示:
Compiling...
test.cpp
c:\test.cpp(8):incomplete here...
Linking...
test.exe - 0 error(s), 0 warning(s)
宏prompt(desc)的作用就是显示其被调用的位置,上面的__LINE__从MSDN中可以查到其在ANSI Predefined Macros中的说明如下:
The line number in the current source file. The line number is a decimal integer constant. It can be altered with a #line directive.
如是,宏prompt(desc)就是对__LINE__用#做了字符串指示。
现在,可以开始用宏的这个特性实现变参功能了。
约定变参函数定义是:
void MyPrint( int iFirst, … )
{
va_list marker;
int i = first;
va_start( marker, first ); /* Initialize variable arguments. */
while ( -1 != i )
{
cout << i << endl;
i = va_arg( marker, int );
}
va_end( marker ); /* Reset variable arguments. */
}
没错!变参宏的定义其实就是像下面的这么简单:
#define _MYPRINT(x) MyPrint(x)
但还需要的是对传的参数做个directive
#define _ARGC(x) x##,
#define _LAST_ARGC(x) x
#define _ARGC_END _LAST_ARGC(-1)
宏_MYPRINT(x)的调用方法如下:
int x1 = 1, x2 = 2, x3 = 3;
//…
_MYPRINT(_ARGC(x1)_ARGC(x3)_ARGC_END);
_MYPRINT(_ARGC(x1)_ARGC(x2)_ARGC(x3)_ARGC_END);
屏幕输出:
1
3
1
2
3
OK!大功告成J
在下面的内容,你还将看到将变参宏与C++ 模板功能结合使用的具体示例——变参宏在临界类型模板类TCriticalType 中的使用。
由于篇幅所限,我这里列出的将只是TCriticalType 的精简版本,完整代码可以从我的个人网站下载(http://www.personsoft.com/yxd/CriticalType.h),如果你还不能下载,那请稍等,我一有时间就会上传进去,并请注意URL的大小写。
TCriticalType实现的功能是提供一个受临界保护的变量类型
比如
#typedef TCriticalType<int> c_int;
//…
c_int ciTest;
ciTest的用法将和普通int类型变量的用法相似,但是其受临界保护。下面就是TCriticalType的可运行的部分实现代码清单(请注意红色字体部分):
template < class RawType >
class TCriticalType
{
public:
TCriticalType();
RawType GetValue() const; // 这里由于读操作的特点,我决定使用变参宏
void BeginGet(); // 进入读临界保护
void EndGet(); // 退出读临界保护
void SetValue( const RawType data ); // 进入写临界保护区,完成写操作并退出
~TCriticalType();
protected:
CRITICAL_SECTION m_cs; // critical section
RawType m_data; // protected data
};
template < class RawType >
TCriticalType< RawType >::TCriticalType()
{
InitializeCriticalSection( &m_cs );
}
template < class RawType >
TCriticalType< RawType >::~TCriticalType()
{
DeleteCriticalSection( &m_cs );
}
template < class RawType >
void TCriticalType< RawType >::BeginGet()
{
EnterCriticalSection( &m_cs );
}
template < class RawType >
void TCriticalType< RawType >::EndGet()
{
LeaveCriticalSection( &m_cs );
}
template < class RawType >
RawType TCriticalType< RawType >::GetValue() const
{
return m_data;
}
template < class RawType >
void TCriticalType< RawType >::SetValue( RawType data )
{
__try
{
EnterCriticalSection( &m_cs );
m_data = data;
}
__finally
{
LeaveCriticalSection( &m_cs );
}
}
// 变参模板函数定义
//------>BEGIN
template < class RawType >
static void BeginGetMulti( const RawType* pFirst, ... )
{
RawType* pos = ( RawType* )pFirst;
va_list marker;
va_start( marker, pFirst );
while ( NULL != pos )
{
pos->BeginGet();
pos = va_arg( marker, RawType* );
}
va_end( marker );
}
template < class RawType >
static void EndGetMulti( const RawType* pFirst, ... )
{
RawType* pos = ( RawType* )pFirst;
va_list marker;
va_start( marker, pFirst );
while ( NULL != pos )
{
pos->EndGet();
pos = va_arg( marker, RawType* );
}
va_end( marker );
}
//<------END
#define _BeginGetSingle(object) __try{ object.BeginGet();
#define _EndGetSingle(object) }__finally{ object.EndGet(); }
// 以下是变参宏的定义
//------>BEGIN
#define OBJ(x) &x##,
#define OBJ_END NULL
#define _BeginGetMulti(objlist) __try{ BeginGetMulti(objlist);
#define _EndGetMulti(objlist) }__finally{ EndGetMulti(objlist); }
//<------END
typedef TCriticalType<int> c_int;
typedef TCriticalType<short> c_short;
typedef TCriticalType<long> c_long;
typedef TCriticalType<unsigned int> c_uint;
typedef TCriticalType<unsigned short> c_ushort;
typedef TCriticalType<unsigned long> c_ulong;
typedef TCriticalType<char> c_char;
typedef TCriticalType<TCHAR> c_TCHAR;
typedef TCriticalType<WORD> c_WORD;
typedef TCriticalType<DWORD> c_DWORD;
typedef TCriticalType<bool> c_bool;
typedef TCriticalType<BOOL> c_BOOL;
typedef TCriticalType<float> c_float;
typedef TCriticalType<double> c_double;
TCriticalType的使用方法(同样请注意读操作部分):
class CCriticalUser
{
public:
CCriticalUser() {}
~CCriticalUser() {}
void Write();
void Read();
protected:
c_int m_ciTest1;
c_int m_ciTest2;
c_char m_ccTest3;
c_bool m_cbTest4;
}
void CCriticalUser::Write()
{
m_ciTest1.SetValue( 1 );
m_ciTest2.SetValue( 2 );
m_ccTest3.SetValue( ‘A’ );
m_cbTest4.SetValue( true );
}
void CCriticalUser::Read()
{
int iTest1, iTest2;
char cTest3;
BOOL bTest4;
_BeginGetMulti(OBJ(&m_ciTest1)OBJ(&m_ciTest2)OBJ(&m_ccTest3)OBJ(&m_cbTest4)OBJ_END);
iTest1 = m_ciTest1.GetValue();
iTest2 = m_ciTest2.GetValue();
cTest3 = m_ccTest3.GetValue();
bTest4 = m_cbTest4.GetValue();
cout << iTest1 << endl;
cout << iTest2 << endl;
cout << cTest3 << endl;
cout << bTest4 << endl;
_EndGetMulti(OBJ(&m_ciTest1)OBJ(&m_ciTest2)OBJ(&m_ccTest3)OBJ(&m_cbTest4)OBJ_END);
}
int main( int argc, char* argv[] )
{
CCriticalUser theUser;
theUser.Write();
theUser.Read();
return 0;
}
运行结果,屏幕输出:
1
2
A
1
如上,在CCriticalUser的成员方法Read()中的批读处理操作中,我为了使__try与__finally配对而使用了_BeginGetMulti(objlist)、_EndGetMulti(objlist)和相关的宏。同时你也可以看到_BeginGetMulti(objlist)、_EndGetMulti(objlist)和C++ 模板的结合使其仿佛沾上了泛型的光芒(当然,宏本身就无类型)。
最后,我的观点是少用、慎用宏,其实对本文的例子,GP会是更好的方法,但是正如对C++ 的一个非常恰当的比喻——C++ 就像一把双刃剑,所以就要求用“剑”的人有很高的技艺,才能写出高质量的程序。这篇文章你权且可当作是奇技淫巧,但或许能给你丝毫启发J。
全文完,欢迎指正,谢谢!
P.S. 文中源程序代码在VC6.0环境中编译通过