Sylar C++高性能服务器学习记录01 【日志管理-知识储备篇】
早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频。
由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去。
每每想起倍感惋惜,遂提笔再续前缘。
为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923
B站视频:https://b23.tv/YusP39I
日志管理-知识储备
知识点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 XX
default:
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(&buf,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 main(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 main(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 main(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:0x563d1eb8d154
xxxXXXXXXXX
X= 1
X:0x563d1eb8d15c
xxxxxxxxXXX
X = 2X:0x563d1eb8d154
xxXXXXXXXXX
x1:0x563d1eb8d152
x2:0x563d1eb8d158
x11: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