muduo网络库核心代码阅读(copyable,noncopyable,Logger)(1)

前言

Muduo 是一个基于 Reactor 模式的高性能 C++ 网络库,由陈硕大佬开发,主要用于 Linux 环境下的多线程服务器编程。它强调易用性和高性能,适合开发高并发的网络应用。

由于muduo库没有大规模的模版元编程和泛型编程(这里不得不点名一下Boost)以及其他非常负责抽象的设计,同时作为一个网络框架,设计的又非常易用和高效。因此非常适合初学者来阅读学习。这个系列就是博主参考网上其他文章和视频(特别学习了施磊老师的视频)一步一步阅读muduo核心代码的一个过程,旨在基于代码进行解析muduo的设计。

copyable,noncopyable是muduo库的两个重要接口,前者用来显示指出类的值属性,后者用来禁止拷贝构造和拷贝赋值,能够有效提升项目中程序设计的规范性。而Logger是muduo日志库的核心类,通过格式化日志信息,帮助开发者有效调试程序,定位问题。

copyable和noncopyable

copyable源码(中文注释是我的补充)

#ifndef MUDUO_BASE_COPYABLE_H       //防止重复包含
#define MUDUO_BASE_COPYABLE_H

namespace muduo     //命名空间
{

// A tag class emphasises the objects are copyable.(标签类)
// The empty base class optimization applies.(空基类优化)
// Any derived class of copyable should be a value (值类型)
type.
class copyable
{
protected:
    copyable() = default;       //默认保护构造函数
    ~copyable() = default;      //默认保护析构函数
};

}  // namespace muduo

#endif  // MUDUO_BASE_COPYABLE_H
  • 标签类:没有成员变量,且构造函数为为protected,只能被子类调用,强调其继承对象是可拷贝的。
  • 空基类优化:基类没有成员变量,编译器会优化其大小为0,减少内存占用。
  • 值类型:派生类应该都为值类型。

noncopyable源码

#ifndef MUDUO_BASE_NONCOPYABLE_H        //防止重复包含
#define MUDUO_BASE_NONCOPYABLE_H

namespace muduo     //命名空间
{

class noncopyable
{
public:
//删除拷贝构造和拷贝赋值,其派生子类只能移动
noncopyable(const noncopyable&) = delete;
void operator=(const noncopyable&) = delete;

protected:
//保护构造和析构,无法直接实例化
noncopyable() = default;
~noncopyable() = default;
};

}  // namespace muduo

#endif  // MUDUO_BASE_NONCOPYABLE_H

禁止拷贝构造和拷贝赋值,其派生子类只能移动。避免子类产生多个实例化或者多个对象引用管理资源,适用于单例类、资源管理类等。

通过 copyable 和 noncopyable,可以显式地表达类的设计意图,使代码更易于理解和维护。

Logger

logger类是muduo网络库的日志类,用于记录程序运行时的日志信息,格式化输出。

LogLevel

//定义了日志级别
enum LogLevel
{
    TRACE,    //跟踪信息
    DEBUG,      //调试信息
    INFO,       //一般信息
    WARN,      //警告
    ERROR,      //错误
    FATAL,       //崩溃
    NUM_LOG_LEVELS,      //日志级别数量
};

宏定义

muduo日志库通过宏定义来简化日志输出,总共有八个级别的宏定义,其中前三个级别的使用受当前全局日志级别影响,需要用户手动设置。

#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()
#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
muduo::Logger(__FILE__, __LINE__).stream()
#define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream()
#define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream()
#define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream()
#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()

__FILE__是当前源代码的文件名,包括路径;__LINE__是当前源代码的行号;__func__是当前函数名;他们都是C/C++预定义宏。Logger::stream()返回一个LogStream对象的引用,由于重载了<<运算符,因此可以通过<<接受信息,和std::cout一致。

构造函数

Logger(SourceFile file, int line);
Logger(SourceFile file, int line, LogLevel level);
Logger(SourceFile file, int line, LogLevel level, const char* func);
Logger(SourceFile file, int line, bool toAbort);

实现

Logger::Logger(SourceFile file, int line)
: impl_(INFO, 0, file, line)
{
}

Logger::Logger(SourceFile file, int line, LogLevel level, const char* func)
: impl_(level, 0, file, line)
{
impl_.stream_ << func << ' ';
}

Logger::Logger(SourceFile file, int line, LogLevel level)
: impl_(level, 0, file, line)
{
}

Logger::Logger(SourceFile file, int line, bool toAbort)
: impl_(toAbort?FATAL:ERROR, errno, file, line)
{
}

SourceFile 是Logger的内部类,用于从文件路径中提取文件名
impl_ 是日志的具体实现类,用于构建日志内容等

Impl

Impl是Logger的内部类,用于实现日志的具体内容,包括日志级别、时间、文件名、行号、内容等。

class Impl
{
public:
    typedef Logger::LogLevel LogLevel;
    Impl(LogLevel level, int old_errno, const SourceFile& file, int line);
    void formatTime();
    void finish();

    Timestamp time_;        
    LogStream stream_;      
    LogLevel level_;        //日志级别
    int line_;              //行号
    SourceFile basename_;       //文件名
};

Timestamp是muduo实现的时间戳类,用于记录并格式化当前时间
LogStream是muduo实现的日志流类,重载了<<运算符

析构函数

由于宏定义语句中构造的是匿名类,因此会在调用结束后直接进行析构。
muduo通过析构函数完成最后的输出,将日志内容拷贝到标准输出。

#include <iostream>

class A {
public:
    A() {
        std::cout << "the constructor of A is called" << std::endl;
    }

    ~A() {
        std::cout << "the destructor of A is called" << std::endl;
    }

    void print() {
        std::cout << "the print function of A is called" << std::endl;
    }
};

#define Func A().print();

int main() {
    Func;
    std::cout << "the main function is end" << std::endl;
    return 0;
}

输出:
the constructor of A is called
the print function of A is called
the destructor of A is called //先执行析构
the main function is end

析构函数实现
Logger::~Logger()
{
    //最后往这条日志消息里面插入:-文件名:行数\n
    impl_.finish();
    //获取日志数据的引用
    const LogStream::Buffer& buf(stream().buffer());
    //输出日志内容
    g_output(buf.data(), buf.length());
    //如果日志级别是FATAL,则刷新缓冲区并调用abort()函数
    if (impl_.level_ == FATAL)
    {
        g_flush();
        abort();
    }
}

g_output 和 g_flush

Logger::OutputFunc g_output = defaultOutput;
Logger::FlushFunc g_flush = defaultFlush;

OutPutFunc和FlushFunc是Logger类中定义的两个函数指针:

typedef void (*OutputFunc)(const char* msg, int len);
typedef void (*FlushFunc)();

不同调用对象可以让日志输出到不同的地方

默认输出
void defaultOutput(const char* msg, int len)
{
size_t n = fwrite(msg, 1, len, stdout);     //写到标准输出
//FIXME check n
(void)n;    //忽略未使用对象,避免编译警告
}
默认刷新
void defaultFlush()
{
fflush(stdout);     //刷新标准输出缓冲区
}
posted @   xiaodao0036  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示