Easylogging的封装使用二

来源:互联网 发布:大专网络教育怎么样 编辑:程序博客网 时间:2024/05/21 13:59

一.对Char类型的处理
在Easylogging的封装使用一:http://blog.csdn.net/woshichenweixian/article/details/77278488 中讲到,对char类型的输出需要进行一些特殊处理,最简单的处理是在最终输出char类型数据时进行一个强制转换,转成整形数据。
Easylogging中log数据的输出是在其类class Writer中实现的。在该类中对各种数据类型进行了<<符号重载中,这里截几个数据类型的输出如下:
 inline Writer& operator<<(const std::string& log_) {        if (!proceed_) { return *this; }        _ELPP_STREAM(logger_) << log_;        return *this;    }    inline Writer& operator<<(char log_) {        if (!proceed_) { return *this; }        _ELPP_STREAM(logger_) << log_;        return *this;    }    inline Writer& operator<<(bool log_) {        if (!proceed_) { return *this; }        _ELPP_STREAM(logger_) << log_;        return *this;    }    inline Writer& operator<<(signed short log_) {        if (!proceed_) { return *this; }        _ELPP_STREAM(logger_) << log_;        return *this;    }

分别将std::string,char,bool,signed short数据类型输出到_ELPP_STREAM(logger_)中,展开该宏可以看到是返回logger_的stream成员:
#define _ELPP_STREAM(l) (*(l->stream()))
logger_的stream是std::stringstream指针类型:
std::stringstream* stream_;
所以,我们的log数据最终都被放进标准库的string流中,在这之前,可改变要输出的数据的类型。例如,将char转成整形:
inline Writer& operator<<(char log_) {        if (!proceed_) { return *this; }        _ELPP_STREAM(logger_) << static_cast<int>(log_);        return *this;    }
通过static_cast将char数据转成int类型。

二.对数组的处理
在《Easylogging的封装使用一》中给出了输出数组的封装,其同样有char类型乱码的问题,同样可通过强转避免:
template<class _TYPE>void logDump(char *info,_TYPE *buff ,int size,LEVEL logLevel)                                                                                       {                                                                                                                             std::string str(info) ;                                                                                                        str += ": " ;                                                                                                             if(buff == NULL || size <=0)                                                                                                                                                                                                                     str+="buff is null!" ;                                                                                                                                                                                                                        else                                                                                                                      {                                                                                                                         std::stringstream ss ;                                                                                                     for(int _dumpArrSize = 0; _dumpArrSize < size ; ++_dumpArrSize)                                                                                                                                                                           ss << static_cast<int>(buff[_dumpArrSize]) << "," ;                                                                                                                                                                                    str += ss.str() ;                                                                                                     }                                                                                                                         logStrOut(size , str ,logLevel) ;                                     }
这种方式太过粗暴了,任何数据类型都被强转,特别是不管是unsigned还是unsigned的类型都被强转成int。要解决这个问题,可使用C++的模板特例化特性,只对char *,unsigned char *,const char * ,const unsigned char * 这四个类型进行特例化处理:
template<>void logDump(char *info,char *buff ,int size,LEVEL logLevel);                                                                                       template<>void logDump(char *info,unsigned char *buff ,int size,LEVEL logLevel);                                                                                       template<>void logDump(char *info,const char *buff ,int size,LEVEL logLevel);                                                                               template<>void logDump(char *info,const unsigned char *buff ,int size,LEVEL logLevel); 
然后在各自的特例化函数中对数据进行特殊处理。这样就避免了任何数据类型都直接进行int强转的粗暴处理,而只对几个char类型进行强转处理。

三.对struct,class类类型的封装
对Easylogging最满意的特性就是支持类类型的数据输出。例如,要输出下面的一个数据类型:
struct tagTest{int aa ;char cc ;};

在以往,只能对其所有的数据成员一个一个输出:
tagTest test ;LogDebug<<test.aa ;LogDebug<<test.cc ;

如果这个数据类型有十几二十个数据成员,那每次输出都必定是很麻烦的。为了解决这个麻烦,我们可以为该类型定义一个<<符号重载函数,然后就可以像内置数据类型一样,直接输出该数据类型的对象了:
struct tagTest{int aa ;char cc ;friend std::ostream& operator<<(std::ostream &out_ , const tagTest &obj_){out_ <<"aa:" <<obj_.aa <<",cc:"<<obj_.cc;return out_ ;}};

为什么多了一个符号重载函数就可以直接输出类类型的对象呢?其实是因为在log输出的实现之处,Writer类中实现了一个模板:
  template <class Class>    inline Writer& operator<<(const Class& class_) {        if (!proceed_) { return *this; }        _ELPP_STREAM(logger_) << class_;        return *this;    }
最终,自定义的类的输出将会进入该模板函数的一个特例中,然后经过_ELPP_STREAM(logger_) << class_找到自定义类的<<操作符函数中。
现在,我们可以这样输出我们定义的类类型对象了:
tagTest test ;test.aa = 1 ;test.cc = 2 ;LogDebug<<"test:"<<test ;

输出结果如下:
[19/08/2017 16:50:59.142] :test:aa:1,cc:
aa的值为1,咦,怎么cc的值没有输出?注意看cc的类型为char,所以其实这里也有char类型的问题,导致输出了ascii值为1的字符,其实是乱码来的。现在把cc的类型改成int类型,就可以看到输出的值是正常的了:
struct tagTest{int aa ;int cc ;friend std::ostream& operator<<(std::ostream &out_ , const tagTest &obj_){out_ <<"aa:" <<obj_.aa <<",cc:"<<obj_.cc;return out_ ;}};
[19/08/2017 17:13:32.232] :test:aa:1,cc:2

所以,我们还需要解决类成员数据中char类型的输出问题。这个问题在后面会继续讲解怎么解决。现在先来看看如何对<<重载函数的定义进行封装,使得定义类的<<重载函数更简单:
//用于重载类的log输出#define OUTER   out_ #define OBJ     obj_#define BEGIN_OPERATOR_LOG_OUT(ObjType)                                                                                       \friend std::ostream& operator<<(std::ostream &OUTER , const ObjType &OBJ)                                             \{                                                                              #define END_OPERATOR_LOG_OUT                                                                                                  \return OUTER ;                                                                                                    \}#define  LOG_FOR_MEMBER(_mem)            MYLOG::logOut(OUTER , OBJ._mem);
template<class _TYPE>void logOut(std::ostream &outer , _TYPE var){outer << var << ",";}

现在定义类的输出就更简单了:
struct tagTest{int aa ;int cc ;BEGIN_OPERATOR_LOG_OUT(tagTest)LOG_FOR_MEMBER(aa)LOG_FOR_MEMBER(bb)END_OPERATOR_LOG_OUT};

如果类的成员变量类型也是类类型,那怎么办?其实只需对该类型也进行<<重载就可以了。现在给tagTest添加一个类类型成员变量:
struct tagTT{int t1 ;int t2BEGIN_OPERATOR_LOG_OUT(tagTT)LOG_FOR_MEMBER(t1)LOG_FOR_MEMBER(t2)END_OPERATOR_LOG_OUT};struct tagTest{int aa ;int cc ;tagTT tt ;BEGIN_OPERATOR_LOG_OUT(tagTest)LOG_FOR_MEMBER(aa)LOG_FOR_MEMBER(bb)LOG_FOR_MEMBER(tt)END_OPERATOR_LOG_OUT};

可以看到,只需对新的类类型也进行 <<操作符的定义,就可以实现类类型的嵌套。
如果类的数据成员是数组呢?为了解决这个问题,需要再封装一个对数组的输出:
#define LOG_FOR_ARRAY_CONSTANT(_count,_arr)      MYLOG::logArrOut(OUTER , _count , OBJ._arr) ;
template<class _TYPE>void logArrOut(std::ostream &outer, int count , _TYPE *arr){outer<<"<";                                                                                                                                 for(int _iArrSize = 0 ; _iArrSize < count ; ++_iArrSize)                                                                                                                                                                                                                   outer <<arr[_iArrSize] << ",";                                                                                                                                                                                                                                            outer<<">" ;        }

给tagTest再添加一个数组成员,然后看看其怎么输出的:
struct tagTest{int aa ;int cc ;tagTT tt ;int ary_[5] ;BEGIN_OPERATOR_LOG_OUT(tagTest)LOG_FOR_MEMBER(aa)LOG_FOR_MEMBER(bb)LOG_FOR_MEMBER(tt)LOG_FOR_ARRAY_CONSTANT(5,ary_)END_OPERATOR_LOG_OUT};

也是只需再添加一行代码就可以输出数组成员了,所以还是很方便的。还有一点也很实用的,数组的成员也可以是类类型,只需对该类类型进行<<重载的实现。所以,目前来说,普通的数据包所包含的数据类型都可以很方便的输出。

最后,来看看上面遗留的问题:char数据乱码的问题。我们不能直接对数据成员进行强转成int类型,因为有一些数据成员是类类型,不能直接进行强转;如果图方便,可以再定义一个宏专门处理char类型的,当这样就需要多记住一个宏了,使用起来不是很方便。
最好的解决方式是和前面的数组一样,对char类型进行模板特例化,即我们只需对这两个模板函数:
template<class _TYPE>void logOut(std::ostream &outer , _TYPE var)
template<class _TYPE>void logArrOut(std::ostream &outer, int count , _TYPE *arr)
然后在特例化函数中对特殊的数据类型进行特殊处理:
//下面的几个函数用来特化char类型,因为char的输出是其字符,但实际上数据包中期望的是输出数值。template<>void logOut(std::ostream &outer ,char var);template<>void logOut(std::ostream &outer ,unsigned char var);
template<>void logArrOut(std::ostream &outer, int count , char *arr);template<>void logArrOut(std::ostream &outer, int count , unsigned char *arr);template<>void logArrOut(std::ostream &outer, int count , const char *arr);template<>void logArrOut(std::ostream &outer, int count , const unsigned char *arr);


对Easylogging的使用经过自己的封装后,会方便很多,特别是在不同的项目中,根据项目的使用要求可以进行不同的封装。
如果对这份封装代码感兴趣的,可以联系我。

下一节将会对Easylogging的源码进行分析。










原创粉丝点击