文章目录
一、std::format
GCC 13, CLANG 14 and MSVC 16.10/VS 2019 all have the {fmt} based std::format available in respective standard libraries.
基本字符串格式化:
cpp
#include <format>
#include <iostream>
int main() {
std::string message = std::format("Hello, {}!", "World");
std::cout << message << std::endl; // 输出: Hello, World!
return 0;
}
多个参数:
cpp
#include <format>
#include <iostream>
int main() {
std::string message = std::format("{}, you have {} new messages", "Alice", 5);
std::cout << message << std::endl; // 输出: Alice, you have 5 new messages
return 0;
}
指定宽度和填充:
cpp
#include <format>
#include <iostream>
int main() {
std::string message = std::format("{:*>10}", 42);
std::cout << message << std::endl; // 输出: *******42
return 0;
}
格式说明符
- std::format 支持多种格式说明符,类似于 printf,但更灵活和强大。
整数:
cpp
#include <format>
#include <iostream>
int main() {
std::string dec = std::format("{:d}", 42); // 十进制
std::string hex = std::format("{:x}", 42); // 十六进制
std::string oct = std::format("{:o}", 42); // 八进制
std::cout << "Decimal: " << dec << std::endl; // 输出: Decimal: 42
std::cout << "Hexadecimal: " << hex << std::endl; // 输出: Hexadecimal: 2a
std::cout << "Octal: " << oct << std::endl; // 输出: Octal: 52
return 0;
}
浮点数:
cpp
#include <format>
#include <iostream>
int main() {
std::string fixed = std::format("{:.2f}", 3.14159); // 定点表示,保留两位小数
std::string sci = std::format("{:.2e}", 3.14159); // 科学计数法
std::cout << "Fixed: " << fixed << std::endl; // 输出: Fixed: 3.14
std::cout << "Scientific: " << sci << std::endl; // 输出: Scientific: 3.14e+00
return 0;
}
对齐和填充:
cpp
#include <format>
#include <iostream>
int main() {
std::string left = std::format("{:<10}", "left"); // 左对齐
std::string right = std::format("{:>10}", "right"); // 右对齐
std::string center = std::format("{:^10}", "center"); // 居中对齐
std::cout << "Left: " << left << std::endl; // 输出: Left: left
std::cout << "Right: " << right << std::endl; // 输出: Right: right
std::cout << "Center: " << center << std::endl; // 输出: Center: center
return 0;
}
指定填充字符:
cpp
#include <format>
#include <iostream>
int main() {
std::string padded = std::format("{:*>10}", 42); // 用 '*' 填充,右对齐
std::cout << padded << std::endl; // 输出: *******42
return 0;
}
套格式化:
- 你可以将格式化字符串作为参数传递,进行嵌套格式化:
cpp
#include <format>
#include <iostream>
int main() {
std::string nested = std::format("Result: {}", std::format("{:.2f}", 3.14159));
std::cout << nested << std::endl; // 输出: Result: 3.14
return 0;
}
自定义类型格式化:
- 你可以通过定义 formatter 特化来格式化自定义类型:
- 为 Point 结构体特化 std::formatter 模板。特化的模板需要实现两个函数:parse 和 format。
cpp
#include <format>
#include <iostream>
// 定义 Point 结构体
struct Point {
int x, y;
};
// 为 Point 特化 std::formatter 模板
template <>
struct std::formatter<Point> {
// 支持的格式说明符
constexpr auto parse(format_parse_context& ctx) {
// 这里你可以解析特定的格式说明符,如果有的话
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && (*it == 'f' || *it == 'd')) {
++it;
}
// 检查格式字符串是否正确
if (it != end && *it != '}') {
throw format_error("invalid format");
}
// 返回格式说明符的结束位置
return it;
}
// 格式化函数
auto format(const Point& p, format_context& ctx) const {
// 格式化输出
return format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
int main() {
Point p{1, 2};
std::string pointStr = std::format("Point: {}", p);
std::cout << pointStr << std::endl; // 输出: Point: (1, 2)
return 0;
}
二、std::source_location
std::source_location 提供了一种获取编译时源代码位置信息的便捷方法;
std::source_location 是 C++20 引入的一个标准库特性,用于获取代码的编译时信息,如文件名、行号、列号和函数名。这对于调试和日志记录非常有用,因为它可以在运行时捕获这些信息,而不需要手动提供。
基本用法
- std::source_location 类似于传统的预处理器宏(如 FILE 和 LINE),但提供了更灵活和安全的接口。
cpp
#include <iostream>
#include <source_location>
void logMessage(const std::string& message, const std::source_location& location = std::source_location::current()) {
std::cout << "Message: " << message << "\n"
<< "File: " << location.file_name() << "\n"
<< "Line: " << location.line() << "\n"
<< "Column: " << location.column() << "\n"
<< "Function: " << location.function_name() << "\n";
}
int main() {
logMessage("This is a log message");
return 0;
}
std::source_location 类:
- std::source_location 是一个不可变(immutable)的类,它包含了与源代码位置相关的信息。
- 常用的成员函数有:
file_name(): 返回当前文件名的字符串。
line(): 返回当前行号。
column(): 返回当前列号。
function_name(): 返回当前函数名。
在 logMessage 函数中,location 参数的默认值是 std::source_location::current(),它捕获调用 logMessage 时的源代码位置。 这使得调用者无需显式提供源代码位置,编译器会自动提供。
自定义日志宏
- 你可以定义一个宏来简化日志记录,利用 std::source_location 捕获源代码位置。
cpp
#include <iostream>
#include <source_location>
#define LOG_MESSAGE(msg) logMessage(msg)
void logMessage(const std::string& message, const std::source_location& location = std::source_location::current()) {
std::cout << "Message: " << message << "\n"
<< "File: " << location.file_name() << "\n"
<< "Line: " << location.line() << "\n"
<< "Column: " << location.column() << "\n"
<< "Function: " << location.function_name() << "\n";
}
int main() {
LOG_MESSAGE("This is a log message");
return 0;
}
捕获异常位置
- 可以在异常处理时使用 std::source_location 捕获抛出异常的位置,从而提供更详细的错误信息。
cpp
#include <iostream>
#include <stdexcept>
#include <source_location>
void throwError(const std::string& message, const std::source_location& location = std::source_location::current()) {
throw std::runtime_error(
std::format("Error: {}\nFile: {}\nLine: {}\nColumn: {}\nFunction: {}",
message, location.file_name(), location.line(), location.column(), location.function_name()));
}
int main() {
try {
throwError("An example error");
} catch (const std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
三、detail名字空间
details名字空间实现内部函数的封装,对外隐藏细节,只暴露必要API函数
cpp
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <mutex>
#include <queue>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
class ThreadPool {
public:
explicit ThreadPool(size_t numThreads);
~ThreadPool();
template <class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
private:
// Worker threads
std::vector<std::thread> workers;
// Task queue
std::queue<std::function<void()>> tasks;
// Synchronization
std::mutex queueMutex;
std::condition_variable condition;
bool stop;
// Internal implementation details
void worker();
};
// Implementation of ThreadPool methods
ThreadPool::ThreadPool(size_t numThreads) : stop(false) {
for (size_t i = 0; i < numThreads; ++i) {
workers.emplace_back(&ThreadPool::worker, this);
}
}
ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for (std::thread& worker : workers) {
worker.join();
}
}
template <class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
//创建一个 std::packaged_task 对象并包装一个可调用对象(任务)
//它会将任务的结果保存到一个与之关联的 std::promise 对象中。
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queueMutex);
if (stop) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
tasks.emplace([task]() { (*task)(); });
}
condition.notify_one();
return res;
}
void ThreadPool::worker() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) {
return;
}
task = std::move(tasks.front());
tasks.pop();
}
task();
}
}
int main() {
ThreadPool pool(4);
auto result = pool.enqueue([](int answer) { return answer; }, 42);
std::cout << "The answer is " << result.get() << std::endl;
return 0;
}
测试:
bash
Program returned: 0
Program stdout
The answer is 42
std::packaged_task 和 std::future 的结合使用的例子:
cpp
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
// 一个简单的任务函数,返回输入值的两倍
int doubleValue(int x) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟长时间任务
return x * 2;
}
int main() {
// 创建一个 std::packaged_task 对象并包装 doubleValue 函数
std::packaged_task<int(int)> task(doubleValue);
// 获取与该任务关联的 std::future 对象
std::future<int> result = task.get_future();
// 启动一个线程来异步执行该任务
std::thread t(std::move(task), 10);
// 在主线程中可以做其他工作
// 等待任务完成并获取结果
std::cout << "Result: " << result.get() << std::endl;
// 等待线程完成
t.join();
return 0;
}
std::promise 和 std::future 结合使用的例子:
cpp
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
// 一个简单的任务函数,返回输入值的两倍
int doubleValue(int x) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟长时间任务
return x * 2;
}
int main() {
// 创建一个 std::promise 对象
std::promise<int> promise;
// 获取与该 promise 关联的 std::future 对象
std::future<int> result = promise.get_future();
// 启动一个线程来异步执行任务,并使用 lambda 设置 promise 的值
std::thread t([&promise](int x) {
// 执行任务并设置 promise 的值
promise.set_value(doubleValue(x));
}, 10);
// 在主线程中可以做其他工作
// 等待任务完成并获取结果
std::cout << "Result: " << result.get() << std::endl;
// 等待线程完成
t.join();
return 0;
}
四、X-macro技术
X-macro技术的基本思想是使用预处理器宏来定义一组数据或操作,然后通过另一个宏来实际展开这些数据或操作。
eg:假设我们有一组颜色定义,并希望使用这些定义来生成枚举和字符串数组。传统的做法需要分别定义这些内容,可能导致重复代码。
X-macro技术可以帮助我们解决这个问题。
cpp
#include <stdio.h>
//首先,定义一个包含所有颜色的宏列表:
#define COLOR_LIST \
X(RED) \
X(GREEN) \
X(BLUE) \
X(YELLOW)
//接下来,使用这个宏列表生成枚举
//这里,我们定义了一个宏 X,它会在 COLOR_LIST 中被展开,每个颜色都会生成相应的枚举值。
typedef enum {
#define X(color) color,
COLOR_LIST
#undef X
} Color;
//然后,可以使用相同的宏列表生成字符串数组:
const char* ColorNames[] = {
#define X(color) #color,
COLOR_LIST
#undef X
};
int main() {
for (int i = 0; i < sizeof(ColorNames)/sizeof(ColorNames[0]); ++i) {
printf("Color %d: %s\n", i, ColorNames[i]);
}
return 0;
}
测试:
bash
Program returned: 0
Program stdout
Color 0: RED
Color 1: GREEN
Color 2: BLUE
Color 3: YELLOW
五、cpp20的log
旧的打印方法:
cpp
#include <format>
#include <source_location>
#include <iostream>
void log(std::string msg, const char* file, int line)
{
std::cout<< file << ":"<<line<< " [Info] "<<msg<<'\n';
}
#define LOG(msg) log(msg,__FILE__,__LINE__)
int main()
{
LOG("wangji");
return 0;
}
使用source_location,source_location写在默认参数的位置(内部实现buildlocation),实际是打印时是调用者的信息
cpp
#include <format>
#include <source_location>
#include <iostream>
void log(std::string msg, std::source_location loc=std::source_location::current())
{
std::cout<< loc.file_name() << ":"<<loc.line()<< " [Info] "<<msg<<'\n';
}
#define LOG(msg) log(msg)
int main()
{
LOG("wangji");
return 0;
}
结合std::format,把format的参数抄过来
cpp
#include <format>
#include <iostream>
#include <source_location>
template <class T>
struct with_source_location {
private:
T inner;
std::source_location loc;
public:
template <class U>
requires std::constructible_from<T, U>
consteval with_source_location(
U &&inner, std::source_location loc = std::source_location::current())
: inner(std::forward<U>(inner)), loc(std::move(loc)) {}
constexpr T const &format() const { return inner; }
constexpr std::source_location const &location() const { return loc; }
};
template <typename... Args>
void log_info(with_source_location<std::format_string<Args...>> fmt,
Args &&...args) {
auto const &loc = fmt.location();
std::cout << loc.file_name() << ":" << loc.line() << " [Info] "
<< std::vformat(fmt.format().get(),
std::make_format_args(args...))
<< '\n';
}
int main() {
log_info("wangji1999");
return 0;
}
测试:
cpp
Program returned: 0
Program stdout
/app/example.cpp:35 [Info] wangji1999
使用X macro技术来定义日志等级
很神奇的是,最后宏展开后,最后一个枚举后面没有增加逗号
cpp
#include <format>
#include <iostream>
#include <source_location>
#include <cstdint>
#define MINILOG_FOREACH_LOG_LEVEL(f) \
f(trace) \
f(debug) \
f(info) \
f(critical) \
f(warn) \
f(error) \
f(fatal)
enum class log_level : std::uint8_t {
#define _FUNCTION(name) name,
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
};
//cpp17支持inline修饰全局唯一的全局变量
inline std::string log_level_name(log_level lev) {
switch (lev) {
#define _FUNCTION(name) case log_level::name: return #name;
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
}
return "unknown";
}
template <class T>
struct with_source_location {
private:
T inner;
std::source_location loc;
public:
//consteval只能编译器调用,constexpr既可以是编译期也可以是运行期调用
template <class U>
requires std::constructible_from<T, U>
consteval with_source_location(
U &&inner, std::source_location loc = std::source_location::current())
: inner(std::forward<U>(inner)), loc(std::move(loc)) {}
constexpr T const &format() const { return inner; }
constexpr std::source_location const &location() const { return loc; }
};
//cpp17支持inline修饰全局唯一的全局变量
inline log_level max_level=log_level::info;
template <typename... Args>
void generic_log(log_level lev, with_source_location<std::format_string<Args...>> fmt,
Args &&...args)
{
if (lev>= max_level)
{
auto const &loc = fmt.location();
std::cout << loc.file_name() << ":" << loc.line() << " [Info] "
<< std::vformat(fmt.format().get(),
std::make_format_args(args...))
<< '\n';
}
}
//X macro技术封装不同等级的日志函数
#define _FUNCTION(name) \
template <typename... Args> \
void log_##name(with_source_location<std::format_string<Args...>> fmt, Args &&...args) { \
return generic_log(log_level::name, std::move(fmt), std::forward<Args>(args)...); \
}
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
int main() {
generic_log(log_level::debug ,"wangji {}", "hi");
generic_log(log_level::info ,"wangji {}", "hi");
log_debug( "wangji {}", "hi");
log_info("wangji {}", "hi");
return 0;
}
注意:
全局的模板函数和直接在类内定义的成员函数,cpp自动给你加上inline
为了防止多个log模块冲突,需要加上namespace,注意宏是怎么加的,要在内部加;
不想暴露给用户的函数,用details的namespce包起来,但是用户仍然可以拿到
cpp
#include <format>
#include <iostream>
#include <source_location>
#include <cstdint>
#include <string>
#include <fstream>
#include <chrono>
namespace minilog{
#define MINILOG_FOREACH_LOG_LEVEL(f) \
f(trace) \
f(debug) \
f(info) \
f(critical) \
f(warn) \
f(error) \
f(fatal)
enum class log_level : std::uint8_t {
#define _FUNCTION(name) name,
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
};
//不想暴露给用户的函数,用details的namespce包起来,但是用户仍然可以拿到
namespace detail{
//给日志设置颜色,ansi控制码,1m表示强调色
//\E,\033是一样的
#if defined(__linux__) || defined(__APPLE__)
inline constexpr char k_level_ansi_colors[(std::uint8_t)log_level::fatal + 1][8] = {
"\E[37m",
"\E[35m",
"\E[32m",
"\E[34m",
"\E[33m",
"\E[31m",
"\E[31;1m",
};
inline constexpr char k_reset_ansi_color[4] = "\E[m";
#define _MINILOG_IF_HAS_ANSI_COLORS(x) x
#else
#define _MINILOG_IF_HAS_ANSI_COLORS(x)
inline constexpr char k_level_ansi_colors[(std::uint8_t)log_level::fatal + 1][1] = {
"",
"",
"",
"",
"",
"",
"",
};
inline constexpr char k_reset_ansi_color[1] = "";
#endif
inline std::string log_level_name(log_level lev) {
switch (lev) {
#define _FUNCTION(name) case log_level::name: return #name;
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
}
return "unknown";
}
inline log_level log_level_from_name(std::string lev){
#define _FUNCTION(name) if (lev == #name) return log_level::name;
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
return log_level::info;
}
template <class T>
struct with_source_location {
private:
T inner;
std::source_location loc;
public:
template <class U>
requires std::constructible_from<T, U>
consteval with_source_location(
U &&inner, std::source_location loc = std::source_location::current())
: inner(std::forward<U>(inner)), loc(std::move(loc)) {}
constexpr T const &format() const { return inner; }
constexpr std::source_location const &location() const { return loc; }
};
//通过环境变量设置loglevel
inline log_level g_max_level=[]()->log_level{
auto lev=std::getenv("MINILOG_LEVEL");
if (lev)
{
return log_level_from_name(lev);
}
else
{
return log_level::info;
}
}();
//自定义输出文件
inline std::ofstream g_log_file=[]()->std::ofstream{
auto path=std::getenv("MINILOG_FILE");
if (path)
{
return std::ofstream(path, std::ios::app);
}
else
{
return std::ofstream();
}
}();
inline void output_log(log_level lev, std::string msg, std::source_location const &loc) {
//增加时间戳,cpp20
std::chrono::zoned_time now{std::chrono::current_zone(), std::chrono::high_resolution_clock::now()};
msg = std::format("{} {}:{} [{}] {}", now, loc.file_name(), loc.line(), log_level_name(lev), msg);
if (g_log_file) {
g_log_file << msg + '\n';
}
if (lev >= detail::g_max_level) {
std::cout << _MINILOG_IF_HAS_ANSI_COLORS(k_level_ansi_colors[(std::uint8_t)lev] +)
msg _MINILOG_IF_HAS_ANSI_COLORS(+ k_reset_ansi_color) + '\n';
}
}
}
//追加写
inline void set_log_file(std::string path) {
detail::g_log_file = std::ofstream(path, std::ios::app);
}
//一般不直接暴露变量给外面,而是用过某个函数设置日志等级
inline void set_log_level(log_level lev){
detail::g_max_level = lev;
}
template <typename... Args>
void generic_log(log_level lev, detail::with_source_location<std::format_string<Args...>> fmt,
Args &&...args)
{
if (lev>= detail::g_max_level)
{
auto const &loc = fmt.location();
//cout的线程安全问题,多次使用cout可能不是线程安全的,一次使用则是线程安全的
std::cout _MINILOG_IF_HAS_ANSI_COLORS(<< detail::k_level_ansi_colors[(std::uint8_t)lev])
<< loc.file_name() << ":" << loc.line() <<" [" <<detail::log_level_name(lev)<< "] "
<< std::vformat(fmt.format().get(), std::make_format_args(args...))
_MINILOG_IF_HAS_ANSI_COLORS(<< detail::k_reset_ansi_color)<< '\n';
}
}
#define _FUNCTION(name) \
template <typename... Args> \
void log_##name(detail::with_source_location<std::format_string<Args...>> fmt, Args &&...args) { \
return generic_log(log_level::name, std::move(fmt), std::forward<Args>(args)...); \
}
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
//直接用这个宏,这个宏不遵守namspace,定义宏的时候,日志宏名称的前缀使用namespace作为前缀
#define MINILOG_P(x) ::minilog::log_info(#x "={}", x)
}
int main() {
MINILOG_P("100");
::minilog::log_fatal("123");
::minilog::log_debug("123");
minilog::set_log_level(minilog::log_level::trace); // default log level is info
::minilog::log_debug("123");
std::cout<<"============"<<std::endl;
#define _FUNCTION(name) minilog::log_##name(#name);
MINILOG_FOREACH_LOG_LEVEL(_FUNCTION)
#undef _FUNCTION
return 0;
}
测试: