让我们习惯在底层用C++宏生成代码 (转)
我们知道使用C++泛型编程可以写出扩展性非常强的程序,使用C++的接口技术也可以很容易的扩展出已经存在的程序。只不过有一件事情它们可都做不到,那就是使用宏来产生代码。使用宏来生成代码通常对于库作者和底层架构者具有非凡的意义,而在应用层个人认为应该尽量减少它的使用,当然,这并不影响我们去学习并了解它。
那么什么叫“使用宏来产生代码”呢?
首先我提出如下的需求:我现在需要一个函数JoinString,它能接受1到20个不等的参数,并将每一个参数转换成字符串,最后在函数内部把这些字符串拼接起来然后作为一个字符串返回。类似于这样:
string JoinString( 参数列表... )
{
转换
返回参数列表组合成的字符串
}
有人说为什么不用sprintf_s?我说,那是因为它不是类型安全的,我们的目标是类型安全。简单的例子,如果你在格式化字符串中传入的是%s却传入一个int的话,编译器是检查不出来的,但是运行时会出错,这是它的严重缺陷。
如果是你去怎么实现呢?
现在我提出一种实现方法。首先,我们针对每一种类型实现一个具有一个参数的JoinString,比如对于int , char, float甚至Windows的SYSTEMTIME,Ogre的Vector3实现。然后再基于这个参数,实现2个参数的3个参数的....20个参数的。
您头晕了吗?我还好。只不过想着这个工作量就很恶心是吧?想象吧,如果我们要制作10种类型的转换函数,那么一个参数需要10个。2个参数呢?不是20种哦,因为第一个参数和第二个参数可以不同哦,那么他们是10*10 = 100种。好,那么20个不同的参数的JoinString,实现10种类型,或者100种类型。哇靠,那是一个什么数字啊!!!!!!!!!!!!!!
好的,如果您足够聪明,您肯定不会这么干,对吧。好了,让我们来说说第二种方法。
c++有一种模板技术,如果我们使用模板的话,那么会变得很好办。对于一个参数的JoinString,那么它可能的声明是这样的:
template < typename T >
string JoinString( const T& value )
{
返回结果
}
然后我们可以提供分别具有2-20个参数的模板参数。有的朋友会说,不行啊你这样。这样你不是还需要对每个JoinString做特化么?因为对于不同类型的T,转换方法不同啊!!!是的,是这样的,那么问题就聚焦在不同类型的T,具有不同的转换方法。好的,让我们来解决这个问题。
首先,向大家介绍“垫片”的概念。什么是垫片呢?当我们的桌子的四个脚不同高的时候,我们需要拿东西塞在短的脚下面让他们同样的高对不对?那好,这个东西就是垫片。在C++中,也有类似的垫片的概念。比如现在我们需要某种类型的描述,也就是只对字符串感兴趣,那么我们可以写这样一个函数:
string Desc( 类型 ){ 返回描述的字符串 }
然后对不同的类型提供这个函数的重载(使用模板特化亦可),即可在遭遇任何类型的时候我们都能得到它的描述(前提是你实现了它的垫片),这样,函数的调用者永远不需要做任何修改,这样实现了模块间的独立性。
好了让我们来看看一个简单的垫片程序吧~~呵呵。
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
string Desc( int a )
{
ostringstream ostr;
ostr<< a;
return ostr.str();
}
string Desc( double dd )
{
ostringstream ostr;
ostr<< dd;
return ostr.str();
}
void print( string str )
{
cout<< str << endl;
}
void main()
{
print( Desc(100) );
print( Desc(12.4) );
}
关于垫片就说到这里,更多的内容建议大家去看Matthew Wilson的书《Imperfect c++》,上面有提到哦。
好了,现在我们可以用模板来实现我们的JoinString了。我的字符串垫片是这样实现的:
template < typename T >
inline std::string GetString( const T& value )
{
std::string strResult;
strResult = boost::lexical_cast< std::string >(value);
return strResult;
}
#ifndef HERO_BUILD_NOT_WITH_OGRE
template <>
inline std::string GetString( const Ogre::Vector3& value )
{
std::ostringstream ostr;
ostr << value;
return ostr.str();
}
#endif
这是一个基于boost::lexical_cast,因为它可以帮我们实现内置类型到字符串的转换,让我不用自己去做,这样可以节省很多时间哦。(PS:boost是一个优秀的C++程序库,它的主页是:http://www.boost.org)
首先,我们可能需要处理一个接受字符串的函数,然后我们在模板函数中使用GetString<T>(value)获取模板参数的字符串值并传递给我们的真正的处理函数。比如这样:
template <typename T >
class Join_Handler
{
public:
typedef T value_type;
typedef T& reference_type;
public:
Join_Handler( reference_type value ):m_objValue(value)
{
}
public:
template < typename T1 >
inline T& SmartWrite( const T1& t1 )
{
return m_objValue.SmartWirte<T1>( t1 );
}
private:
reference_type m_objValue;
};
// 可以通过特化更多的Join_Handler来支持更多的PIPE。
template <>
class Join_Handler< std::string >
{
public:
typedef std::string value_type;
typedef value_type& reference_type;
public:
Join_Handler( reference_type str ) : m_strPipe(str)
{
}
public:
template < typename T1 >
inline std::string& SmartWrite( const T1& t1 )
{
m_strPipe.append( Hero::GetString<T1>(t1) );
return m_strPipe;
}
private:
reference_type m_strPipe;
};
请允许我为大家介绍一下这个程序的设计思路。首先,这是一个Join_Handler,从字面上来理解就是拼接字符串的处理。为什么要使用模板来实现呢?主要的原因就是为了扩展性,因为我想我的程序最后不仅仅可以拼接到字符串中去还可以品接到其它符合我们接口的Pipe中去比如ostream。
请大家注意上面红色部分的代码,很好理解是吗?它会把我们接受的一个模板类型使用字符串垫片转换成字符串,然后使用C++标准库字符串的append函数拼接到已经存在的字符串上面去。
好了,那么我们的可以实现类似这样的JoinString:
template < typename T1, typename T2 >
string JoinString( const T1& t1, const T2& t2 )
{
string strResult;
Join_Handler< std::string > pipe(strResult);
pipe.SmartWrite( GetString<T1>(t1) );
pipe.SmartWrite( GetString<T2>(t2) );
return strResult;
}
OK了,是不是?好的,让我们去实现一个分别支持1个到20个参数的JoinString吧。是的,这可是一个体力活。当然,这并不是结束的时候,因为现在才进入了我们今天的主题。
首先,要让大家知道一些关于宏的东西。##是什么呢?它就是贴上的意思。#代表了把后面的部分用引号括起来。另外,当一个宏遇到自己时将会停止替换,这是为了防止宏无限递归的出现。还有,宏替换不是一次性就能完成的,有可能需要编译器跑多趟~~
好了,这时候是时候上代码了,请看:
#define FMT_TEMPLATE_PARAM_LIST( z, n, text ) , typename text##n
#define FMT_FUNCTION_PARAM_LIST( z, n, text ) , const text##n& t##n
#define FMT_FUNCTION_CONTENT( z, n, text ) \
hander.SmartWrite( text##n );
#define FMT_DEFINE_TOTAL_FUNCTION( z, n, text ) \
template < typename T, typename T0 BOOST_PP_REPEAT_FROM_TO( 1, n, FMT_TEMPLATE_PARAM_LIST, text ) > \
inline T& WritePipe( T& pipe, const T0& t0 BOOST_PP_REPEAT_FROM_TO( 1, n, FMT_FUNCTION_PARAM_LIST, text ) ) \
{ \
Join_Handler<T> hander(pipe); \
hander.SmartWrite( t0 ); \
BOOST_PP_REPEAT_FROM_TO( 1, n, FMT_FUNCTION_CONTENT, t ) \
return pipe; \
}
// 生成各种参数个数的格式化函数
BOOST_PP_REPEAT_FROM_TO( 1, 20, FMT_DEFINE_TOTAL_FUNCTION, T )
#undef FMT_TEMPLATE_PARAM_LIST
#undef FMT_FUNCTION_PARAM_LIST
#undef FMT_FUNCTION_CONTENT
#undef FMT_DEFINE_TOTAL_FUNCTION
#define FMT_TOSTRING_TEMPLATE_PARAM_LIST( z, n, text ) , typename text##n
#define FMT_TOSTRING_FUNCTION_PARAM_LIST( z, n, text ) , const text##n& t##n
#define FMT_TOSTRING_FUNCTION_CONTENT( z, n, text ) \
Hero::Fmt::WritePipe< std::string, text##n >( strResult, t##n );
#define FMT_TOSTRING_DEFINE_TOTAL_FUNCTION( z, n, text ) \
template < typename T0 BOOST_PP_REPEAT_FROM_TO( 1, n, FMT_TOSTRING_TEMPLATE_PARAM_LIST, text ) > \
inline std::string JoinString( const T0& t0 BOOST_PP_REPEAT_FROM_TO( 1, n, FMT_TOSTRING_FUNCTION_PARAM_LIST, text ) ) \
{ \
std::string strResult; \
BOOST_PP_REPEAT_FROM_TO( 0, n, FMT_TOSTRING_FUNCTION_CONTENT, T ) \
return strResult; \
}
BOOST_PP_REPEAT_FROM_TO( 1, 20, FMT_TOSTRING_DEFINE_TOTAL_FUNCTION, T )
#undef FMT_TOSTRING_DEFINE_TOTAL_FUNCTION
#undef FMT_TOSTRING_FUNCTION_CONTENT
#undef FMT_TOSTRING_FUNCTION_PARAM_LIST
#undef FMT_TOSTRING_TEMPLATE_PARAM_LIST
这个实现是基于boost的预处理库的,因为已经有好的轮子了,为什么我们还要造轮子呢?设计JoinString的思想是这样的,JoinString是基于Fmt::WritePipe来实现的,而它们两个都是用宏来生成的。它们的区别就是WritePipe的第一个参数是一个模板Pipe,这个Pipe只要满足Join_Handler下面的SmartWrite函数就可以了。有了这个Pipe,那么我们就可以把各种类型的数据转换成字符串写进去。
这些实现的重点就是分成模板参数序列、函数参数序列以及函数体生成序列。如上红色的部分。使用宏就可以生成我们需要用手去实现的支持1到20个不等个数的参数的函数。实际上,我们花费不到100行代码,而且很灵活。
另外的一个应用的例子:
namespace Hero
{
class LogManager : public Hero::StaticSingleton< LogManager >
{
DEFINE_STATIC_SINGLETON_CLASS( LogManager )
private:
LogManager()
{
RegisterHandle(
Hero::IOutputHandlerSP(
new Hero::DebugPipe(),
boost::mem_fn(&IOutputHandler::Release)
)
);
RegisterHandle(
Hero::IOutputHandlerSP(
new Hero::ConsolePipe(),
boost::mem_fn(&IOutputHandler::Release)
)
);
SetDefaultFile( "Hero.log" );
m_spDefaultLogFilePipe.reset(
new Hero::FilePipe( m_strDefaultLogFile ),
boost::mem_fn(&IOutputHandler::Release)
);
RegisterHandle( m_spDefaultLogFilePipe );
}
~LogManager()
{
}
public:
inline void RegisterHandle( Hero::IOutputHandlerSP spHandle )
{
m_lstHandles.push_back( spHandle );
}
inline void UnRegisterHandle( Hero::IOutputHandlerSP spHandle )
{
m_lstHandles.remove( spHandle );
}
inline void RemoveAll()
{
m_lstHandles.clear();
}
inline void SetDefaultFile( const std::string& strFile )
{
m_strDefaultLogFile = strFile;
if( !m_spDefaultLogFilePipe )
{
return;
}
boost::shared_ptr< Hero::FilePipe > spFilePipe =
boost::dynamic_pointer_cast< Hero::FilePipe >( m_spDefaultLogFilePipe );
if( !spFilePipe )
{
return;
}
if( !boost::algorithm::iequals( spFilePipe->GetFileName(), strFile ) )
{
spFilePipe->Close();
spFilePipe->Create( strFile );
}
}
public:
inline LogManager& Write( const std::string& strValue, bool bAppendSpace = true )
{
std::for_each(
m_lstHandles.begin(),
m_lstHandles.end(),
boost::bind( &IOutputHandler::Write, _1, strValue, bAppendSpace )
);
return *this;
}
inline LogManager& FormatWrite( const char* strFmt, ... )
{
va_list args;
va_start( args, strFmt );
int iSize = _vscprintf( strFmt, args );
if( iSize <= 0 )
{
return *this;
}
Hero::AutoBuffer<char> buf(iSize+1);
vsprintf_s( buf.GetBuffer(), buf.GetSize() , strFmt, args );
this->Write( buf.GetBuffer(), false );
va_end( args );
return *this;
}
// SEH支持函数
inline int WriteSEHLog(
const char* strFile,
int iLine,
const char* strFunction,
LPEXCEPTION_POINTERS pep
)
{
#define HERO_ENUM_CASE( Info ) \
case Info : \
{ \
strInfo = #Info; \
break; \
}
std::string strInfo;
switch ( pep->ExceptionRecord->ExceptionCode )
{
HERO_ENUM_CASE( EXCEPTION_ACCESS_VIOLATION );
HERO_ENUM_CASE( EXCEPTION_DATATYPE_MISALIGNMENT );
HERO_ENUM_CASE( EXCEPTION_BREAKPOINT );
HERO_ENUM_CASE( EXCEPTION_SINGLE_STEP );
HERO_ENUM_CASE( EXCEPTION_ARRAY_BOUNDS_EXCEEDED );
HERO_ENUM_CASE( EXCEPTION_FLT_DENORMAL_OPERAND );
HERO_ENUM_CASE( EXCEPTION_FLT_DIVIDE_BY_ZERO );
HERO_ENUM_CASE( EXCEPTION_FLT_INEXACT_RESULT );
HERO_ENUM_CASE( EXCEPTION_FLT_INVALID_OPERATION );
HERO_ENUM_CASE( EXCEPTION_FLT_OVERFLOW );
HERO_ENUM_CASE( EXCEPTION_FLT_STACK_CHECK );
HERO_ENUM_CASE( EXCEPTION_FLT_UNDERFLOW );
HERO_ENUM_CASE( EXCEPTION_INT_DIVIDE_BY_ZERO );
HERO_ENUM_CASE( EXCEPTION_INT_OVERFLOW );
HERO_ENUM_CASE( EXCEPTION_PRIV_INSTRUCTION );
HERO_ENUM_CASE( EXCEPTION_IN_PAGE_ERROR );
HERO_ENUM_CASE( EXCEPTION_ILLEGAL_INSTRUCTION );
HERO_ENUM_CASE( EXCEPTION_NONCONTINUABLE_EXCEPTION );
HERO_ENUM_CASE( EXCEPTION_STACK_OVERFLOW );
HERO_ENUM_CASE( EXCEPTION_INVALID_DISPOSITION );
HERO_ENUM_CASE( EXCEPTION_GUARD_PAGE );
HERO_ENUM_CASE( EXCEPTION_INVALID_HANDLE );
default:
strInfo = std::string( "SEH异常, 异常码:" ) + Hero::GetString( pep->ExceptionRecord->ExceptionCode );
break;
}
Fmt::WritePipe( strInfo, " 错误信息:", Hero::GetLastErrorString( pep->ExceptionRecord->ExceptionCode&0x0fffffff ) );
this->SmartWrite( "Hero Log:\n Message: ", strInfo, "\n" );
WriteCallStackLog( NULL, pep->ContextRecord );
return EXCEPTION_EXECUTE_HANDLER;
}
inline void WriteCallStackLog( LPCSTR pcszTitle, PCONTEXT pContext )
{
std::string strInfo;
if( ( NULL != pcszTitle ) && ( 0 != *pcszTitle ) )
{
// 有提示标题信息
strInfo += pcszTitle;
}
strInfo += " 调用堆栈:";
// 初始化dbghelp.dll
HANDLE hProcess = ::GetCurrentProcess() ; // 当前进程句柄
if( !SymInitialize( hProcess , NULL, TRUE ) )
{
// 初始化失败
strInfo += "SymInitialise()调用失败,可能PDB文件完整路径中存在中文";
this->SmartWrite( "Hero Log:\n Message: ", Hero::GetLastErrorString(::GetLastError()) );
}
else
{
STACKFRAME sf ;
::memset( &sf , 0 , sizeof( STACKFRAME ) ) ;
sf.AddrPC.Offset = pContext->Eip ;
sf.AddrPC.Mode = AddrModeFlat ;
sf.AddrStack.Offset = pContext->Esp ;
sf.AddrStack.Mode = AddrModeFlat ;
sf.AddrFrame.Offset = pContext->Ebp ;
sf.AddrFrame.Mode = AddrModeFlat ;
DWORD machineType = IMAGE_FILE_MACHINE_I386 ; // 机器类型
HANDLE hThread = GetCurrentThread() ; // 当前线程句柄
// 输出调用堆栈信息
for( ; ; )
{
// 遍历调用堆栈
if( !StackWalk( machineType , hProcess , hThread , &sf , pContext , 0 , SymFunctionTableAccess , SymGetModuleBase , 0 ) )
{
break ;
}
if( sf.AddrFrame.Offset == 0 )
{
break ;
}
BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ] ;
::memset( symbolBuffer , 0 , sizeof( symbolBuffer ) ) ;
PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer ;
pSymbol->SizeOfStruct = sizeof( symbolBuffer ) ;
pSymbol->MaxNameLen = 1024 ;
// 从地址获得函数信息
DWORD64 symDisplacement = 0 ;
if( SymFromAddr( hProcess , sf.AddrPC.Offset , 0 , pSymbol ) )
{
// 获得函数信息成功
// 获得函数所在文件和行号信息
IMAGEHLP_LINE lineInfo ;
::memset( &lineInfo , 0 , sizeof( lineInfo ) ) ;
lineInfo.SizeOfStruct = sizeof( IMAGEHLP_LINE ) ;
DWORD dwLineDisplacement = 0 ;
if( SymGetLineFromAddr( hProcess , sf.AddrPC.Offset , &dwLineDisplacement , &lineInfo ) )
{
// 获得文件和行数信息成功
// 函数名 , 文件名 : 行号
Fmt::WritePipe( strInfo, pSymbol->Name, " ", lineInfo.FileName, "Line: ", lineInfo.LineNumber, "\n" );
}
else
{
// 获得文件和行数信息失败
Fmt::WritePipe( strInfo, "\t", pSymbol->Name );
}
}
else
{
// 获得函数信息失败
// 很大的可能是该exe所在目录下没有相应的pdb文件
strInfo = "调用::SymFromAddr()失败,请检查pdb文件是否存在!或者是否存在中文路径\n";
}
}
// 清理dbghelp.dll
SymCleanup( hProcess ) ;
}
this->SmartWrite( "Hero Log:\n Message: ", strInfo, "\n" );
}
// 生成具有分别具有1到20个参数的SmartWrite
#define HERO_TEMPLATE_PARAM_LIST( z, n, text ) , typename text##n
#define HERO_FUNCTION_PARAM_LIST( z, n, text ) , const text##n& t##n
#define HERO_FUNCTION_CONTENT( z, n, text ) this->Write( GetString( text##n ), false );
#define HERO_SMART_WRITE_SUPPORT_MAX_PARAM_COUNT 20
#define HERO_DEFINE_TOTAL_FUNCTION( z, n, text ) \
template < typename T0 BOOST_PP_REPEAT_FROM_TO( 1, n, HERO_TEMPLATE_PARAM_LIST, text ) > \
inline LogManager& SmartWrite( const T0& t0 BOOST_PP_REPEAT_FROM_TO( 1, n, HERO_FUNCTION_PARAM_LIST, text ) ) \
{ \
BOOST_PP_REPEAT_FROM_TO( 0, n, HERO_FUNCTION_CONTENT, t ) \
\
return *this; \
}
// 生成模板函数参数个数从1到20个的SmartWrite成员函数
BOOST_PP_REPEAT_FROM_TO( 1, HERO_SMART_WRITE_SUPPORT_MAX_PARAM_COUNT, HERO_DEFINE_TOTAL_FUNCTION, T )
#undef HERO_TEMPLATE_PARAM_LIST
#undef HERO_FUNCTION_PARAM_LIST
#undef HERO_FUNCTION_CONTENT
private:
// 所有需要Debug消息的Handler都可以注册到manager中
std::list< Hero::IOutputHandlerSP > m_lstHandles;
std::string m_strDefaultLogFile;
Hero::IOutputHandlerSP m_spDefaultLogFilePipe;
};
DECLARE_STATIC_SINGLETON_VALUE( LogManager )
#if !defined(_DEBUG)
#define HERO_USE_SIMPLE_LOG
#endif
#ifdef HERO_USE_SIMPLE_LOG
#define HERO_SYSTEM_LOG
#else
#define HERO_SYSTEM_LOG , " File: ", __FILE__, "\n Function: ", __FUNCTION__, "\n Line: ", __LINE__, "\n"
#endif
#define LogFmtMsg( fmt, ... ) \
Hero::LogManager::GetSingleton().SmartWrite( Hero::GetTimeString() HERO_SYSTEM_LOG , " Msg:" ); \
Hero::LogManager::GetSingleton().FormatWrite( fmt, __VA_ARGS__ ); \
Hero::LogManager::GetSingleton().Write( "\n", false )
#ifdef HERO_DEBUG
#define LogMsg( ... ) \
Hero::LogManager::GetSingleton().SmartWrite( Hero::GetTimeString() HERO_SYSTEM_LOG , " Msg: ", __VA_ARGS__ ); \
Hero::LogManager::GetSingleton().Write( "\n", false )
#else
#define LogMsg( ... ) \
Hero::LogManager::GetSingleton().SmartWrite( Hero::GetTimeString() HERO_SYSTEM_LOG , " Msg:", __VA_ARGS__ ); \
Hero::LogManager::GetSingleton().Write( "\n", false )
#endif
#define LogLineBoundary \
Hero::LogManager::GetSingleton().Write( "-----------------------------------------------------------------------", true )
#define HERO_SEH_HANDLE \
Hero::LogManager::GetSingletonPtr()->WriteSEHLog( __FILE__, __LINE__, __FUNCTION__, GetExceptionInformation() )
}
这是一个日志系统,如果您对这一系列的程序感兴趣的话可以参考这个:
http://ishare.iask.sina.com.cn/f/6122708.html
这是一份未修改过的代码,您无法使用它直接编译,因为我有在项目中作编译预处理的,而预处理文件貌似没上传。不过代码还是可以看的,看的是思想。(PS:该代码并没有经过完整的测试,比如说是否能够支持20个参数等等,其实大多数时候参数是很少会超过10个的,呵呵)。
其实整篇文章只是简单颜色了宏生成代码的作用,但是却很少提及它的原理,其一是因为我自己对此的领会并没有达到“豁然开朗”的高度,其二也是没有足够的时间来做这件事情。那么,我相信这篇日志也不会完全没有作用,至少它可以告诉我的朋友们:哦,原来还有这种编程的方式。呵。
宏是一把双刃剑,用到好会获得巨大的利益,就像上面的演示一样。用得不好,呵,你就等着郁闷吧~~关于Boost的预处理库,除了Boost的文档之外,还有另外的一本书的附录有提到,他就是《C++.Template.Metaprogramming》。
关于宏的基本知识和一些基本技巧可以参考这两篇我转贴的好文章:
代码自动生成-宏带来的奇技淫巧:http://hi.baidu.com/_%E2d_%B7%B3_%DE%B2%C2%D2/blog/item/0a33517be6aaadfe0bd18730.html
代码自动生成-宏递归思想:http://hi.baidu.com/_%E2d_%B7%B3_%DE%B2%C2%D2/blog/item/a7f7a31948bbe070dbb4bd31.html
相信看过这两篇文章之后,大家再回头看这篇日志会有不同的感受。另外对于最开始的格式化字符串的例子,其实有一个很强大的库可以做这个事情,它就是Matthew Wilson写的fastformat,地址是:http://www.fastformat.org/ 它需要Matthew Wilson的stlsoft库的支持。效率非常高呵呵。