编辑本项目由 岩禾溪 原创
项目实战+新特性用法介绍
开源代码+博客解析+视频讲解
GitHub+CSDN+BiliBili同步更新,三个平台同名【岩禾溪】
视频讲解和代码链接在文章末尾,你的关注是我更新的最大动力
项目环境
本项目采用C++20开发 精简Muduo网络库
Build Tool:Xmake
archlinux-2023.09.01-x86_64
gcc version 13.2.1 20230801 (GCC)
xmake
构建工具简介 xmake 是一个灵活且功能强大的构建工具,支持多种编程语言,特别适用于 C/C++ 项目。
xmake.lua 配置文件
target("cpp")
set_kind("binary")
add_files("*.ixx","*cpp")
set_languages("c++20")
xmake.lua 是 xmake 的配置文件,在这里定义了项目的构建规则和参数。 项目构建规则
target("cpp"):定义项目名称。 set_kind("binary"):指定构建生成的是可执行文件。 add_files(".ixx", "cpp"):添加项目源文件。 set_languages("c++20"):设置使用 C++20 标准编译代码。 C++20 支持
通过 set_languages("c++20"),我们启用了 C++20 标准的支持,使得项目可以利用最新的 C++ 特性。 简洁高效的构建
xmake 简化了构建流程,使得项目的构建过程更加简洁高效。
nocopyable
export module nocopyable;
export class nocopyable{
public:
nocopyable(const nocopyable&) = delete;
nocopyable& operator=(const nocopyable&) = delete;
protected:
nocopyable() = default;
~nocopyable() = default;
};
-
模块定义:
使用export module nocopyable;
定义了一个名为nocopyable
的 C++20 模块。 -
禁止复制:
nocopyable
类中的nocopyable(const nocopyable&) = delete;
和nocopyable& operator=(const nocopyable&) = delete;
阻止了对象的复制和赋值。通过删除拷贝构造函数和拷贝赋值运算符,该类确保不会被复制创建或赋值。 -
受保护的构造函数和析构函数:
构造函数和析构函数声明为protected
,这意味着它们只能被该类及其派生类内部访问。这使得该类可以被继承,但不允许直接创建类的对象或对其进行复制。
该 nocopyable
模块是一种常见的技术,用于设计不希望被复制或赋值的类,例如单例模式的类或管理资源的类。
Logger
Logger.cpp
import Logger;
import Timestamp;
import <iostream>;
import <format>;
// 获取日志唯一的实例对象
Logger &Logger::instance()
{
static Logger logger;
return logger;
}
// 设置日志级别
void Logger::setLogLevel(LogLevel level)
{
logLevel_ = level;
}
template <typename... Args>
requires(std::convertible_to<Args, std::string> && ...)
void Logger::log(LogLevel level,
const std::string &format,
Args &&...args)
{
// 设置日志级别
setLogLevel(level);
// 构建日志消息可以无所谓,不能无所获
std::string message = std::format("[{}] {} : {}", level, Timestamp::now().toString(), std::format(format, std::forward<Args>(args)...));
// 打印日志消息到控制台
std::cout << message << std::endl;
}
这段代码展示了一个简单的日志记录器 Logger
的部分实现,利用模块化方式组织。这个模块包含了日志记录的实例获取、日志级别设置和日志记录功能。
-
模块导入:
import Logger;
语句导入了Logger
模块 -
获取日志实例对象:
Logger::instance()
函数返回唯一的日志实例。 -
设置日志级别:
Logger::setLogLevel(LogLevel level)
函数用于设置日志级别。 -
日志记录功能:
Logger::log()
函数实现了日志记录的功能,根据传入的日志级别和格式进行记录。
Logger.ixx
export module Logger;
import <string>;
import nocopyable;
import <concepts>;
export class Logger : public nocopyable
{
public:
enum class LogLevel
{
INFO, // 普通信息
ERROR, // 错误信息
FATAL, // core信息
DEBUG, // 调试信息
};
static Logger &instance();
void setLogLevel(LogLevel level);
template <typename... Args>
requires(std::convertible_to<Args, std::string> && ...)
void log(LogLevel level, const std::string &format, Args &&...args);
private:
LogLevel logLevel_ = LogLevel::INFO;
};
-
模块化定义:
通过export module Logger;
定义了一个名为Logger
的 C++20 模块,但前面的module;
是无效的。 -
模块导入:
import <string>;
和import nocopyable;
语句导入了相应的模块和头文件。 -
日志记录器类:
Logger
类是一个日志记录器类,继承了nocopyable
,表明该类不可被复制。 -
日志级别定义:
enum class LogLevel
定义了几个日志级别:INFO、ERROR、FATAL、DEBUG。 -
获取日志实例和设置日志级别:
instance()
函数用于获取日志唯一的实例对象。setLogLevel()
函数用于设置日志级别。 -
日志记录功能:
log()
函数实现了根据传入的日志级别、格式和参数进行日志记录的功能。
template <typename... Args>
requires(std::convertible_to<Args, std::string> && ...)
void log(LogLevel level, const std::string &format, Args &&...args);
在这段代码中,使用了C++20中的模板约束(template constraints)特性。模板约束允许你对模板参数进行更精细的限制,以确保模板参数满足特定的条件。在你提供的函数签名中,使用了 requires
关键字并结合了 std::convertible_to
的概念约束(concept constraint)。
让我们来解释这个函数签名中的不同部分:
-
template <typename... Args>
:这声明了一个模板函数,它接受零个或多个模板参数,并将它们命名为Args
。 -
requires
:这是C++20中的关键字,用于引入模板约束。它后面紧跟着模板约束表达式,用于对模板参数进行约束。 -
(std::convertible_to<Args, std::string> && ...)
:这是模板约束表达式的一部分。std::convertible_to
是C++20中的概念(concept),用于检查类型是否可以转换为另一种类型。在这里,std::convertible_to<Args, std::string>
检查模板参数Args
是否可以隐式转换为std::string
类型。&& ...
是展开运算符,表示对参数包中的每个元素应用这个概念。
因此,这个模板函数 log
只允许接受满足以下条件的参数:
-
模板参数
Args
的每个类型必须能够隐式转换为std::string
类型。
这样的约束可以确保在调用 log
函数时,传递的参数能够隐式转换为 std::string
,这符合函数签名中 format
参数为 const std::string &
类型的要求。
Timestamp
Timestamp.ixx
export module Timestamp;
export import <string>;
// 时间类
export class Timestamp
{
public:
Timestamp();
explicit Timestamp(int64_t microSecondsSinceEpoch);
static Timestamp now();
std::string toString() const;
private:
int64_t microSecondsSinceEpoch_;
};
-
模块化定义:
通过export module Timestamp;
定义了一个名为Timestamp
的 C++20 模块,但前面的module;
是无效的,应该指定模块的名称。 -
头文件包含:
#include <cstdint>
和#include <string>
:这些是标准头文件,可能用于定义时间类所需的类型和字符串处理。 -
时间类定义:
Timestamp-
类定义了一个时间类,封装了一些关于时间处理的功能:
-
std::string toString() const
返回时间的字符串表示形式。 -
静态函数
static Timestamp now()
用于获取当前时间; -
参数构造函数
explicit Timestamp(int64_t microSecondsSinceEpoch)
; -
默认构造函数
Timestamp()
;
-
Timestamp.cpp
import Timestamp;
import <ctime>;
Timestamp::Timestamp() : microSecondsSinceEpoch_(0) {}
Timestamp::Timestamp(int64_t microSecondsSinceEpoch) : microSecondsSinceEpoch_(microSecondsSinceEpoch) {}
Timestamp Timestamp::now()
{
return Timestamp(time(nullptr));
}
std::string Timestamp::toString() const
{
char buf[128] = {0};
time_t timeValue = static_cast<time_t>(microSecondsSinceEpoch_ / 1000000);
std::tm result;
if (localtime_r(&timeValue, &result) != nullptr)
{
snprintf(buf, 128, "%4d/%02d/%02d %02d:%02d:%02d",
result.tm_year + 1900,
result.tm_mon + 1,
result.tm_mday,
result.tm_hour,
result.tm_min,
result.tm_sec);
}
return buf;
}
-
函数定义:
实现了-
Timestamp
类中声明的函数:
-
std::string Timestamp::toString() const
返回时间的字符串表示形式。 -
静态函数
Timestamp Timestamp::now()
用于获取当前时间; -
参数构造函数
Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
; -
默认构造函数
Timestamp::Timestamp()
;
-
-
时间格式化:
Timestamp::toString()
函数将时间戳格式化为字符串形式,使用localtime_r
和snprintf
函数进行格式化。这段代码是一个Timestamp
类中的成员函数toString()
的实现,它用于将时间戳转换为字符串表示形式。在这个函数中:-
修改返回类型为
std::string
,可以确保时间戳的字符串副本被正确地返回,而不是指向局部数组的悬挂指针。 -
char buf[128]
:定义了一个大小为 128 的字符数组buf
,用于存储格式化后的时间戳字符串。 time_t timeValue = static_cast<time_t>(microSecondsSinceEpoch_ / 1000000)
:将类成员变量microSecondsSinceEpoch_
转换为秒级的时间戳,存储在timeValue
中。std::tm result
:创建一个std::tm
结构体,用于存储时间的分解信息。if (localtime_r(&timeValue, &result) != nullptr)
:使用localtime_r()
函数将时间戳timeValue
转换为本地时间,并将结果存储在result
中。localtime_r()
函数会将时间戳转换为本地时间,并填充result
结构体,如果转换成功,则返回result
结构体指针,否则返回nullptr
。snprintf(buf, 128, "%4d/%02d/%02d %02d:%02d:%02d", result.tm_year + 1900, result.tm_mon + 1, result.tm_mday, result.tm_hour, result.tm_min, result.tm_sec)
:使用snprintf
函数将时间分解信息按照指定的格式写入buf
数组中。这里的格式字符串"%4d/%02d/%02d %02d:%02d:%02d"
将年月日时分秒格式化为字符串,并将其写入到buf
中。return buf
:返回格式化后的时间戳字符串。但是需要注意的是,返回的是buf
的指针,并且在函数返回时,buf
数组将超出其作用域,这可能导致未定义的行为。为了避免此问题,可以考虑返回一个std::string
对象,而不是指向局部数组的指针。
-
总结
可以无所谓,不能无所获
——岩