当前位置:首页 » 《随便一记》 » 正文

Sylar C++高性能服务器学习记录01 【日志管理-知识储备篇】

11 人参与  2024年05月26日 12:48  分类 : 《随便一记》  评论

点击全文阅读


早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频。
由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去。
每每想起倍感惋惜,遂提笔再续前缘。

为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923

日志管理-知识储备

知识点01 (switch使用宏定义简化)

在日常编码中我们经常使用枚举
不仅可以统一管理,还能提高代码可读性,还能避免代码写错
如下是一个水果枚举类型

enum Fruit {UNKNOW = 0,//未知CHERRIES = 1,//车厘子STRABERRY = 2,//草莓APPLE = 3,//苹果WATERMELON = 4//西瓜};

但是往往会有这样的情况,因为枚举中的值我们赋与的是数字 所以他方便运算但是不利于展示

//例如:我们想要输出对应枚举时 看到的是 3 而不是 APPLE std::cout << Fruit::APPLE << std::endl;

有时我们在数据存储时想要存储具体数值
但是在显示输出时 希望看到的是对应的枚举字段
那么我们就需要使用以下代码来进行输出时的转化

//这个方法名称我喜欢使用2来替换to fruit2string==fruitToString const char* fruit2string(Fruit fruit){switch(fruit){case fruit::CHERRIES:return "CHERRIES";break;case fruit::STRABERRY:return "STRABERRY";break; case fruit::APPLE:return "APPLE";break; case fruit::WATERMELON:return "WATERMELON";break; default:return "UNKNOW";break;}return "UNKNOW";}

以上的写法没有问题 但是在C++中 并不 “优雅”
所以,以下是在Sylar中常用的手法,可以说非常优雅

const char* fruit2string(Fruit fruit){switch(fruit){#define XX(name) \case fruit::name:\return #name;\//注意 #name 是将对应的参数名称变成了字符串(这个是核心)break;//宏定义是简单的文本替换 所以这里请不要加双引号,错误写法 XX("APPLE")XX(CHERRIES)XX(STRABERRY)XX(APPLE)XX(WATERMELON)#undef XXdefault:return "UNKNOW";break;}return "UNKNOW";}

知识点02 (va_list 与 vasprintf 配合实现C风格的输出)

在c++中我们会用以下方式来进行输出

const std::string name = "XYZ";int age = 28;std::cout << "Name: " << name << " " << "Age: " << age << std::endl;

但是很多人喜欢c的输出方式,这样更加简洁

#include <stdio.h>const char* name = "XYZ";int age = 28;printf("Name: %s Age: %d \n",name,age);

在sylar中有一个格式化的方法可以使用C风格的方式来进行字符串的格式化
先解释以下为什么C中有printf我们还要自己实现一个格式化
因为我们要的是格式化好了之后的字符串 而不是输出格式化后的内容
所以我们需要使用 va_list 和 vasprintf来实现一个format方法返回格式化后的字符串

#include <sstream>#include <stdarg.h>std::string format(const char* fmt,...){std::stringstream ss;va_list args;va_start(args,fmt);char* buf = nullptr;int len = vasprintf(&fmt,fmt,args);if(len != -1){ss<<std::string(buf,len);free(buf);}va_end(args);return ss.str();}

知识点03 (巧用匿名对象析构函数进行流式输出)有点RAII的意思在里面

标题不太好理解,用以下例子来解释
假设我们需要一个日志对象来提供日志输出的方法,将指定字符串打印到控制台显示
那么我们常规方法如下:

#include <iostream>//日志对象class Logger {public://构造Logger();//析构~Logger();//输出日志void log(const std::string& msg);};Logger::Logger(){std::cout << "Logger()" << std::endl;}Logger::~Logger(){std::cout << "~Logger()" << std::endl;}void Logger::log(const std::string& msg){std::cout << msg << std::endl;}int man(int argc,char** argv){std::cout << "===start===" << std::endl;Logger lg;lg.log("Good good study, day day up!");std::cout << "====end====" << std::endl;return 0;}

以下是对应的输出

------------------------------------------------===start===Logger()Good good study, day day up!====end====~Logger()------------------------------------------------

可以看到我们需要 lg.log(“Good good study, day day up!”);的方式来进行输出,这样并不方便
我们希望的是 lg.getSS() << “Hello” << " " << “Sylar” << “\n”; 这样的方式来连续调用
我们需要有 std::stringstream 来存储内容 并且在析构时输出
以下进行改造

#include <iostream>#include <sstream>//日志对象class Logger {public://构造Logger();//析构~Logger();std::stringstream& getSS() { return m_ss; }private:std::stringstream m_ss;};Logger::Logger(){std::cout << "Logger()" << std::endl;}Logger::~Logger(){std::cout << m_ss.str() << std::endl;std::cout << "~Logger()" << std::endl;}int man(int argc,char** argv){std::cout << "===start===" << std::endl;Logger lg;lg.getSS() << "hello" << " " << "sylar";std::cout << "====end====" << std::endl;return 0;}

以下是对应的输出 输出位置是不理想的,在程序结束后对象才析构,所以输出后置了

------------------------------------------------===start===Logger()====end====hello sylar~Logger()------------------------------------------------

可以看到在作用域外才能进行输出,那么输出位置将无法精准
这里其实要注意:匿名对象的析构时机和具名对象的析构时机是不一样的
匿名对象:执行完后立即析构
具名对象(栈内):超出作用域后析构
具名对象(堆内):手动释放后析构
我们可以利用匿名对象及时析构的特点,让输出时机变得精准
以下代码只需改动main方法中的调用部分即可

int man(int argc,char** argv){std::cout << "===start===" << std::endl;//改成匿名对象Logger().getSS() << "hello" << " " << "sylar";std::cout << "====end====" << std::endl;return 0;}

以下是对应的输出 输出位置是精准的

------------------------------------------------===start===Logger()hello sylar~Logger()====end====------------------------------------------------

但是这里需要 Logger().getSS() 的方式 其实还是不方便
我们更希望使用:SYLAR_LOG << “Hello”; 的方式来输出
所以我们可以定义一个宏来完成:

#define SYLAR_LOG Logger().getSS()int man(int argc,char** argv){std::cout << "===start===" << std::endl;//改成宏调用SYLAR_LOG << "hello" << " " << "sylar";std::cout << "====end====" << std::endl;return 0;}

以下是对应的输出 可以看到效果是一样的但是更加舒服了

------------------------------------------------===start===Logger()hello sylar~Logger()====end====------------------------------------------------

知识点04 (巧用模板类实现单例)

在sylar中有一个单例的模板类,一共20几行代码却需要细细琢磨
十分精湛的写法,让人叹为观止
以下代码十年的功力,不知阁下如何应对
singleton.h

#ifndef __SYLAR_SINGLETON_H_#define __SYLAR_SINGLETON_H_namespace sylar {//X 为了创造多个实例对应的Tag//N 同一个Tag创造多个实例索引template<class T, class X = void, int N = 0>class Singleton {public:    static T* GetInstance() {        static T v;        return &v;    }};//X 为了创造多个实例对应的Tag//N 同一个Tag创造多个实例索引template<class T, class X = void, int N = 0>class SingletonPtr {public:    static std::shared_ptr<T> GetInstance() {        static std::shared_ptr<T> v(new T);        return v;    }};}#endif

这个类不太好解释,以下是我网上找的例子希望能帮助大家理解

#include<iostream>#include<stdio.h>using namespace std;class X{public:void func() { cout << "XXXXXXXXXXX" << endl; }};class Y{public:void func() { cout << " YYYYYYYYYY" << endl; }};template<class T, class X = void, int N = 0>class Singleton{public:static T* GetInstance(){static T v;static int x;x++;printf("x = %d\tX: %p\n", x, &x);return &v;}};int main(int argc,char** argv){X* x1 = Singleton<X>::GetInstance();x1->func();X* x2 = Singleton<X, Y>::GetInstance();x2->func();X* x11 = Singleton<X>::GetInstance();x11->func();printf("%p\n%p\n%p\n", x1, x2, x11);return 0;}

以下是输出内容

X =1X:0x563d1eb8d154xxxXXXXXXXXX= 1X:0x563d1eb8d15cxxxxxxxxXXXX = 2X:0x563d1eb8d154xxXXXXXXXXXx1:0x563d1eb8d152x2:0x563d1eb8d158x11:0x563d1eb8d152

可以看到 只要模板参数一致那么得到的对象就是同一个也就是单例
模板参数 T 是需要被单例化的类 必传的
模板参数 X 可以理解为 标签 这一类标签下的单例
模板参数 N 可以理解为下标 这一类标签下的 n号单例
其实严格来说后面两个参数的存在 违背了单例的定义 但是实际开发中确实是能够用到
可以说这个模板类 包含了单例的功能 又增强了单例的功能
这里举个例子(不一定恰当)

背景:一家公司在 A市 B市 C市 都各自有一个子公司每个子公司下都有: 行政部门:Adm1 Adm2技术部门:Tec1 Tec2财务部门:Fin1 Fin2需求: 在整个公司系统中,每个子公司的每个部门需要有单独的管理类来进行管理方式://A市行政部1号管理类A* mgr_a_adm_1 = Singleton<A, Adm, 1>::GetInstance(); //B市技术部2号管理类    B* mgr_b_tec_2 = Singleton<B, Tec, 2>::GetInstance();    ...

知识点05(时间戳的获取和人性化展示)

int main(int argc,char** argv){    const std::string m_format = "%Y-%m-%d %H:%M:%S";    struct tm tm;    time_t t = time(0);    localtime_r(&t, &tm);    char buf[64];    strftime(buf, sizeof(buf), m_format.c_str(), &tm);    std::cout << buf << std::endl;return 0;}

以下是输出

2024-04-20 04:38:57

知识点06 获取程序在系统中的线程ID(和linux中的PID对应)

#include <unistd.h>#include <sys/types.h>#include <sys/syscall.h>int main(int argc,char** argv){    std::cout << syscall(SYS_gettid) << std::endl;    return 0;}
下一篇:【日志管理-代码分析篇】该篇篇幅较长,建议做完眼保健操后食用

QQ交流群:957100923


点击全文阅读


本文链接:http://zhangshiyu.com/post/113827.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1