三套 C++ 微服务常用库,逐个接口拆给你看。每个接口统一格式:函数签名 → 参数表 → 代码片段 → 运行结果。 所有代码来自 /home/dev/workspace/ 真实工程,所有运行结果是实际跑出来的。
第一部分:gflags(命令行参数解析)
1.1 DEFINE_bool / DEFINE_int32 / DEFINE_string / DEFINE_double — 定义命令行参数
函数签名
DEFINE_bool(参数名, 默认值, 帮助描述);
DEFINE_int32(参数名, 默认值, 帮助描述);
DEFINE_string(参数名, 默认值, 帮助描述);
DEFINE_double(参数名, 默认值, 帮助描述);
参数表
|
参数名 |
标识符 |
后面用 FLAGS_xxx 访问 |
|
默认值 |
对应类型 |
用户不传命令行时用这个值 |
|
帮助描述 |
字符串 |
-help 时显示 |
代码片段(gflags/main.cc:14-17)
DEFINE_bool(reuse_addr, true, "使用启用地址重用选项");
DEFINE_int32(listen_port, 8080, "服务器监听窗口");
DEFINE_string(listen_ip, "0.0.0.0", "服务器监听ip");
DEFINE_double(PI, 3.14, "圆周率");
运行结果
# 默认值运行
./main
1 ← bool true 输出 1
8080 ← int32 默认值
0.0.0.0 ← string 默认值
3.14 ← double 默认值
# 命令行覆盖
./main -listen_port=9090 -reuse_addr=false
0 ← false
9090 ← 覆盖
0.0.0.0
3.14
1.2 DECLARE_string / DECLARE_int32 / DECLARE_bool — 跨文件声明参数
函数签名
DECLARE_string(参数名); // 在 A.cc 定义的 string 参数,B.cc 要访问就用 DECLARE
DECLARE_int32(参数名);
DECLARE_bool(参数名);
DECLARE_double(参数名);
作用:DEFINE 只能出现一次(哪个文件定义都行),其他文件要访问同一个 FLAGS_xxx,就先用 DECLARE 声明。
代码片段(gflags/child.cc:4)
// child.cc 里要访问 main.cc 定义的 listen_ip
DECLARE_string(listen_ip);
void Print() {
std::cout << FLAGS_listen_ip << std::endl; // 跨文件访问
}
运行结果
1.3 google::ParseCommandLineFlags — 解析命令行
函数签名
bool ParseCommandLineFlags(int* argc, char*** argv, bool remove_flags);
参数表
|
argc |
int* |
指针,会被改写 |
|
argv |
char*** |
指针,会被改写 |
|
remove_flags |
bool |
true=解析完后把 flag 从 argv 里删掉 |
返回值:成功返回 true。
代码片段(gflags/main.cc:23)
int main(int argc, char* argv[]) {
google::ParseCommandLineFlags(&argc, &argv, true);
// 之后就能用 FLAGS_xxx 访问参数了
}
运行结果
./main -listen_port=9090 # 命令行参数被解析
./main –flagfile=main.conf # 也支持从配置文件读取(见下)
1.4 –flagfile= — 从配置文件读参数
代码片段(gflags/main.conf)
–listen_ip=0.0.0.0
–reuse_addr=false
–listen_port=9090
–PI=3.1415926
运行结果
./main –flagfile=main.conf
0 ← 来自 conf
9090 ← 来自 conf
0.0.0.0 ← 来自 conf
3.14159 ← 来自 conf(double 截断显示)
0.0.0.0 ← child.cc Print()
1.5 gflags 小结:命令行参数优先级
配置文件 –flagfile < 命令行直接传参
即:配置文件里写的可以被命令行覆盖。
gflags 工程文件一览
|
main.cc |
DEFINE + Parse + 直接访问 FLAGS |
|
child.h |
声明 extern void Print() |
|
child.cc |
DECLARE + 跨文件访问 FLAGS |
|
main.conf |
配置文件 |
|
Makefile |
g++ main.cc child.cc -o main -lgflags |
第二部分:gtest(单元测试框架)
2.1 testing::InitGoogleTest — 初始化测试框架
函数签名
void InitGoogleTest(int* argc, char** argv);
作用:解析 gtest 自己的命令行参数(比如 –gtest_filter),必须在 RUN_ALL_TESTS() 之前调用。
代码片段(所有 gtest 工程 main 都一样)
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
// … AddGlobalTestEnvironment 等 …
return RUN_ALL_TESTS();
}
2.2 RUN_ALL_TESTS() — 执行所有测试用例
作用:扫描所有 TEST / TEST_F 宏注册的用例,逐个执行并输出结果。
代码片段
return RUN_ALL_TESTS(); // main 里的 return 值
2.3 TEST(测试套件, 用例名) — 定义普通测试用例
宏展开
#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)
参数表
|
test_suite_name |
测试套件名(分组) |
|
test_name |
用例名 |
代码片段(gtest/simple.cc:13-35)
TEST(unordered_map_set, insert) {
std::unordered_map<int, int> map;
map.insert(std::make_pair(1, 1));
map.insert(std::make_pair(2, 2));
map.insert(std::make_pair(3, 3));
ASSERT_EQ(map.size(), 3);
}
TEST(unordered_map_test, insert) {
std::unordered_map<int, int> map;
map.insert(std::make_pair(1, 1));
map.insert(std::make_pair(2, 2));
map.insert(std::make_pair(3, 3));
ASSERT_EQ(map.size(), 3);
}
TEST(unordered_map_test, find) {
std::unordered_map<int, int> map;
map.insert(std::make_pair(1, 1));
map.insert(std::make_pair(2, 2));
ASSERT_NE(map.find(1), map.end());
ASSERT_EQ(map.find(1)->second, 1);
}
运行结果
2.4 ASSERT_EQ / ASSERT_NE / ASSERT_TRUE / ASSERT_FALSE — 断言宏
作用:检查条件是否成立。ASSERT_* 是 fatal 断言,失败了当前用例直接终止;EXPECT_* 是 non-fatal,失败了继续往下走。
完整对照表
| ASSERT_EQ(a, b) | a == b | ASSERT_EQ(map.size(), 3); |
| ASSERT_NE(a, b) | a != b | ASSERT_NE(map.find(1), map.end()); |
| ASSERT_LT(a, b) | a < b | ASSERT_LT(1, 2); |
| ASSERT_LE(a, b) | a <= b | ASSERT_LE(map.size(), 10); |
| ASSERT_GT(a, b) | a > b | ASSERT_GT(2, 1); |
| ASSERT_GE(a, b) | a >= b | ASSERT_GE(map.size(), 3); |
| ASSERT_TRUE(x) | x == true | ASSERT_TRUE(map.empty()); |
| ASSERT_FALSE(x) | x == false | ASSERT_FALSE(flag); |
四个类别的助记
| EQ | 等于 | Equal |
| NE | 不等于 | Not Equal |
| LT | 小于 | Less Than |
| LE | 小于等于 | Less or Equal |
| GT | 大于 | Greater Than |
| GE | 大于等于 | Greater or Equal |
| TRUE / FALSE | 布尔判断 | — |
代码片段
ASSERT_EQ(map.size(), 3);
ASSERT_NE(map.find(1), map.end());
ASSERT_EQ(map.find(1)->second, 1);
2.5 testing::Test + TEST_F — 局部测试套件(Fixture)
是什么? 当多个用例共享相同的初始化/清理逻辑时,继承 testing::Test 定义一个 Fixture 类,用 TEST_F 代替 TEST。
Fixture 生命周期
SetUpTestCase() ← 所有用例之前执行一次(类级)
├── SetUp() ← 每个用例之前执行
│ ├── 测试用例体
│ └── TearDown() ← 每个用例之后执行
├── SetUp()
│ ├── 测试用例体
│ └── TearDown()
TearDownTestCase() ← 所有用例之后执行一次(类级)
代码片段(gtest/local.cc:14-48)
class LocalTestSuite : public testing::Test {
public:
static void SetUpTestCase() {
std::cout << "公共环境初始接口–会在所有测试用例之前执行\\n";
}
static void TearDownTestCase() {
std::cout << "公共环境清理接口–会在所有测试用例之后执行\\n";
}
virtual void SetUp() {
std::cout << "执行于每个用例之前" << std::endl;
map.insert(std::make_pair(1, 1));
map.insert(std::make_pair(2, 2));
map.insert(std::make_pair(3, 3));
}
virtual void TearDown() {
std::cout << "执行于每个用例之后" << std::endl;
map.clear();
}
public:
std::unordered_map<int, int> map;
};
TEST_F(LocalTestSuite, find) {
ASSERT_EQ(map.size(), 3);
ASSERT_EQ(map.find(1)->second, 1);
map.erase(1);
std::cout << "测试用例1" << std::endl;
}
运行结果
2.6 testing::Environment + AddGlobalTestEnvironment — 全局测试环境
是什么? 和 Fixture 的区别:Fixture 管一个测试套件的生命周期,Environment 管整个测试程序的生命周期。所有套件共享。
Environment 生命周期
Environment::SetUp() ← 所有测试套件、所有用例之前执行一次
├── 套件1 SetUpTestCase → SetUp → 用例1 → TearDown → TearDownTestCase
├── 套件2 SetUpTestCase → SetUp → 用例2 → TearDown → TearDownTestCase
Environment::TearDown() ← 所有测试套件、所有用例之后执行一次
代码片段(gtest/global.cc:22-50)
// 全局变量,所有测试共用
std::unordered_map<int, int> g_map;
class GlobalTestEnvironment : public testing::Environment {
public:
virtual void SetUp() override {
std::cout << "执行于所有例子之前" << std::endl;
g_map.insert(std::make_pair(1, 1));
g_map.insert(std::make_pair(2, 2));
g_map.insert(std::make_pair(3, 3));
}
virtual void TearDown() override {
std::cout << "执行于所有用例之后" << std::endl;
g_map.clear();
}
};
TEST(GlobalMapTest, Insert) {
ASSERT_EQ(g_map.size(), 3);
}
TEST(GlobalMapTest, Find) {
ASSERT_NE(g_map.find(1), g_map.end());
ASSERT_EQ(g_map.find(1)->second, 1);
}
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
testing::AddGlobalTestEnvironment(new GlobalTestEnvironment);
return RUN_ALL_TESTS();
}
运行结果

2.7 gtest 三种测试方式对比
|
普通测试 |
TEST(Suite, Name) |
无共享,每次独立 |
简单、无依赖的测试 |
|
局部套件 |
TEST_F(Fixture, Name) + 继承 testing::Test |
每个套件独立的 SetUp/TearDown |
同套件多个用例共享数据 |
|
全局环境 |
AddGlobalTestEnvironment + 继承 testing::Environment |
整个程序只执行一次 |
所有套件共享的全局资源 |
gtest 工程文件一览
|
simple.cc |
TEST 宏 + ASSERT 断言 |
|
local.cc |
TEST_F + testing::Test Fixture |
|
global.cc |
testing::Environment + AddGlobalTestEnvironment |
|
Makefile |
g++ xxx.cc -o xxx -lgtest |
第三部分:spdlog(高性能日志库)
3.1 spdlog::init_thread_pool — 初始化异步线程池
函数签名
void init_thread_pool(size_t queue_size, size_t n_threads);
参数表
|
queue_size |
size_t |
日志队列能缓存多少条,默认 8192 |
|
n_threads |
size_t |
后台消费线程数,默认 1 |
异步模式必须先调用,否则异步工厂会崩溃!
代码片段(stdout.cc:22)
spdlog::init_thread_pool(8192, 1);
3.2 spdlog::stdout_color_mt<Factory> — 彩色控制台 logger 工厂
函数签名
// 同步版本
std::shared_ptr<logger> stdout_color_mt(const std::string& logger_name, color_mode mode = color_mode::automatic);
// 异步版本(模板参数指定工厂)
template<typename Factory>
std::shared_ptr<logger> stdout_color_mt<Factory>(const std::string& logger_name, color_mode mode = color_mode::automatic);
参数表
|
Factory |
模板参数 |
spdlog::async_factory=异步;不传=同步 |
|
logger_name |
string |
logger 名字,用于 drop / registry 查找 |
|
mode |
color_mode |
automatic=tty 才上色,always=强制,never=不上色 |
代码片段
// 异步
auto logger = spdlog::stdout_color_mt<spdlog::async_factory>("stdout_logger");
// 同步
auto logger2 = spdlog::stdout_color_mt("stdout_logger");
运行结果
[16:42:07][debug ][test.cc:26]: 小明今年18岁 ← 终端彩色高亮
[16:42:07][info ][test.cc:27]: 小红今年19岁
[16:42:07][warning][test.cc:28]: 小刚今年20岁
[16:42:07][error ][test.cc:29]: 小李今年21岁
3.3 spdlog::basic_logger_mt<Factory> — 基础文件 logger 工厂
函数签名
// 同步版本
std::shared_ptr<logger> basic_logger_mt(const std::string& logger_name, const std::string& filename, bool truncate = false);
// 异步版本
template<typename Factory>
std::shared_ptr<logger> basic_logger_mt<Factory>(const std::string& logger_name, const std::string& filename, bool truncate = false);
参数表
|
Factory |
模板参数 |
spdlog::async_factory=异步 |
|
logger_name |
string |
logger 名字 |
|
filename |
string |
日志文件路径 |
|
truncate |
bool |
true=每次清空文件,false=追加(默认) |
代码片段
g_logger = spdlog::basic_logger_mt<spdlog::async_factory>("file_logger", "./app.log");
运行结果
./app -log_path=./test.log -log_level=4
cat test.log
[16:42:07][error ][test.cc:29]: 小李今年21岁
3.4 spdlog::drop — 按名字清理 logger
函数签名
void drop(const std::string& logger_name);
作用:spdlog registry 里不允许同名 logger。重复调用初始化函数时,必须先 drop 旧的,否则抛 logger already exists 异常。
代码片段(log.cc:18-19)
spdlog::drop("stdout_logger");
spdlog::drop("file_logger");
3.5 logger->set_level — 设置日志等级
函数签名
void set_level(level::level_enum log_level);
等级对照表
|
level::trace |
0 |
最细,什么都打 |
|
level::debug |
1 |
调试信息 |
|
level::info |
2 |
一般信息 |
|
level::warn |
3 |
警告 |
|
level::err |
4 |
错误 |
|
level::critical |
5 |
致命错误 |
|
level::off |
6 |
全部关闭 |
代码片段
logger->set_level(spdlog::level::err); // 直接传枚举
logger->set_level(spdlog::level::level_enum(settings.level)); // int 转枚举
运行结果
# 默认 level=1(debug),全部输出
./app
[16:42:07][debug ][test.cc:26]: 小明今年18岁
[16:42:07][info ][test.cc:27]: 小红今年19岁
[16:42:07][warning][test.cc:28]: 小刚今年20岁
[16:42:07][error ][test.cc:29]: 小李今年21岁
[16:42:07][debug ][test.cc:30]: HELLO
# 改成 level=4(err),只留 error
./app -log_level=4
[16:42:07][error ][test.cc:29]: 小李今年21岁
3.6 logger->set_pattern — 设置输出格式
函数签名
void set_pattern(const std::string& pattern);
常用占位符
|
%v |
用户消息 |
小明今年18岁 |
|
%l |
level 全名 |
debug |
|
%-7l |
level 左对齐 7 字符 |
debug |
|
%H:%M:%S |
时分秒 |
16:42:07 |
|
%Y-%m-%d |
年月日 |
2026-09-18 |
|
%n |
logger 名 |
stdout_logger |
代码片段
logger->set_pattern("[%H:%M:%S][%l]: %v"); // stdout.cc 用的
logger->set_pattern("[%H:%M:%S][%-7l]: %v"); // 封装默认,level 对齐
3.7 logger->error / info / warn / debug — 记录日志
函数签名
template<typename… Args>
void error(fmt::format_string<Args…> fmt, Args&&… args);
// info / warn / debug 签名一样
代码片段
logger->error("hello world – {}", i); // 一个占位符
logger->info("{}今年{}岁", "小明", 18); // 两个占位符
logger->debug("HELLO"); // 无占位符
运行结果
./stdout | head -5
[16:42:08][error]: hello world – 0
[16:42:08][error]: hello world – 1
[16:42:08][error]: hello world – 2
[16:42:08][error]: hello world – 3
[16:42:08][error]: hello world – 4
./app
[16:42:07][debug ][test.cc:26]: 小明今年18岁
[16:42:07][info ][test.cc:27]: 小红今年19岁
[16:42:07][warning][test.cc:28]: 小刚今年20岁
[16:42:07][error ][test.cc:29]: 小李今年21岁
3.8 logger->flush — 刷新异步队列
函数签名
void flush();
作用:异步模式日志先进内存队列,flush() 强制落盘/输出。程序退出前调一下,防止崩溃丢日志。
代码片段(stdout.cc:33)
logger->flush();
return 0;
3.9 spdlog1 封装:log_settings + spdlog1_init + DBG/INF/WRN/ERR
log_settings 结构体(log.h:25-30)
struct log_settings {
bool async = false; // 是否启用异步日志
int level = 1; // 0~6 对应 level_enum
std::string format = "[%H:%M:%S][%-7l]: %v"; // 日志格式
std::string path = "stdout"; // "stdout" 或 文件路径
};
spdlog1_init 内部逻辑(log.cc:10-37)
1. async=true → init_thread_pool(8192, 1)
2. drop("stdout_logger") + drop("file_logger")
3. 按 async + path 组合创建 logger
4. set_level + set_pattern
日志宏(log.h:39-44)
#define FMT_PREFIX "[{}:{}]: "
#define DBG(fmt, …) spdlog1::g_logger->debug(FMT_PREFIX fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define INF(fmt, …) spdlog1::g_logger->info (FMT_PREFIX fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define WRN(fmt, …) spdlog1::g_logger->warn (FMT_PREFIX fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define ERR(fmt, …) spdlog1::g_logger->error(FMT_PREFIX fmt, __FILE__, __LINE__, ##__VA_ARGS__)
宏的魔术
-
FMT_PREFIX fmt:相邻字符串编译期自动拼接,零开销。"[{}:{}]: " "{}今年{}岁" → "[{}:{}]: {}今年{}岁"
-
__FILE__ / __LINE__:编译器预定义宏,自动带文件名和行号
-
##__VA_ARGS__:没额外参数时把逗号吃了,也能编译
运行结果
第四部分:三库协作的完整运行链路
4.1 spdlog1 + gflags 链路(test.cc)
main()
├── google::ParseCommandLineFlags ← gflags 解析命令行
├── 组装 log_settings ← 把 FLAGS 搬进配置
├── spdlog1::spdlog1_init(settings) ← 初始化 logger
│ ├── init_thread_pool(async=true 时)← spdlog
│ ├── spdlog::drop × 2 ← spdlog 清理旧 logger
│ ├── stdout_color_mt / basic_logger_mt ← spdlog 创建 logger
│ ├── set_level ← spdlog
│ └── set_pattern ← spdlog
├── DBG / INF / WRN / ERR ← spdlog1 宏展开
│ └── g_logger->debug/info/warn/error ← spdlog 记录日志
└── return 0
4.2 工程文件总览
|
gflags |
/home/dev/workspace/gflags/ |
main.cc + child.cc + main.conf |
g++ main.cc child.cc -o main -lgflags |
|
gtest |
/home/dev/workspace/gtest/ |
simple.cc + local.cc + global.cc |
g++ xxx.cc -o xxx -lgtest |
|
spdlog |
/home/dev/workspace/spdlog/ |
stdout.cc + spdlog1/log.h/log.cc/test.cc |
g++ xxx.cc -o xxx -lspdlog -lpthread -lfmt |
自检清单
|
gflags 编译 |
make clean && make main 通过 |
|
gtest 编译(三个) |
make clean && make ALL 通过 |
|
spdlog1 编译 |
make clean && make 通过 |
|
gflags 默认运行 |
输出 1/8080/0.0.0.0/3.14/0.0.0.0 |
|
gflags 命令行覆盖 |
-listen_port=9090 生效 |
|
gflags 配置文件 |
–flagfile=main.conf 生效 |
|
gtest simple |
3 tests passed |
|
gtest local |
生命周期输出顺序正确(SetUpTestCase→SetUp→用例→TearDown→TearDownTestCase) |
|
gtest global |
Environment 生命周期正确 |
|
spdlog1 默认 |
5 行彩色输出 |
|
spdlog1 -log_level=4 |
只剩 1 行 error |
|
spdlog1 文件输出 |
文件内容正确 |
|
stdout.cc |
10000 条 error 日志 |
|
函数签名 vs 系统头文件 |
InitGoogleTest / AddGlobalTestEnvironment / TEST / TEST_F / Environment / Test / init_thread_pool / stdout_color_mt / basic_logger_mt / drop 全部核对过 |
第五部分:代码如下:



















