CppCon 2014 学习:CHEAP, SIMPLE, AND SAFE LOGGING USING C++ EXPRESSION TEMPLATES

article/2025/6/6 2:58:31

这段代码定义了一个简单的日志宏 LOG,用来在代码里方便地打印调试信息。

代码细节解析:

#define LOG(msg) \if (s_bLoggingEnabled) \std::cout << __FILE__ << "(" << __LINE__ << "): " << msg << std::endl
  • s_bLoggingEnabled 是一个全局开关,控制是否启用日志输出。
  • __FILE____LINE__ 是预定义宏,分别表示当前文件名和当前代码行号。
  • msg 是传入的日志内容,利用 C++ 的流式输出(<<)格式,类型安全且灵活
  • 宏展开后,相当于:
    if (s_bLoggingEnabled)std::cout << "当前文件名(行号): " << msg << std::endl;
    

使用示例:

void foo() {std::string file = "blah.txt";int error = 123;LOG("Read failed: " << file << " (" << error << ")");
}

打印结果示例:

test.cpp(5): Read failed: blah.txt (123)

总结:

  • 方便:只需写 LOG(...),无需重复写打印格式代码。
  • 类型安全:利用 C++ 流操作符,无需手动格式化字符串。
  • 带上下文:自动打印文件名和代码行号,方便定位问题。
  • 可控开关:通过 s_bLoggingEnabled 动态开启或关闭日志。

这段是对之前的 LOG 宏经过预处理(pre-processing)后的展开效果:

解释

  • 预处理器把 LOG("Read failed: " << file << " (" << error << ")"); 展开成了:
    if (s_bLoggingEnabled)std::cout << "foo.cpp" << "(" << 53 << "): " << "Read failed: " << file << " (" << error << ")"<< std::endl;
    
  • 其中 "foo.cpp" 是当前文件名(__FILE__),53 是代码行号(__LINE__)。
  • fileerror 是变量,仍保持流式输出,类型安全。

作用

  • 宏的作用就是把日志输出封装成一条语句,自动附加文件名和行号,方便调试定位。
  • 预处理后你看到的就是具体的 if 判断加标准输出语句。

你给的这段汇编代码是编译器对含有日志宏 LOG(...) 的 C++ 代码生成的机器指令示例,展示了日志语句的具体实现细节。

void foo() {string file = "blah.txt";int error = 123;...movb  g_bLogging(%rip), %altestb %al, %alje  ..B2.14movl  $_ZSt4cout, %edimovl  $.L_2__STRING.3, %esicall  ostream& operator<<(ostream&, char const*)movq  %rax, %rdimovl  $.L_2__STRING.0, %esicall  ostream& operator<<(ostream&, char const*)movq  %rax, %rdimovl  $19, %esicall  ostream::operator<<(int)...movq  %rax, %rdimovl  $ostream& endl(ostream&), %esicall  ostream::operator<<(ostream& (*)(ostream&))
}

解析:

  • movb g_bLogging(%rip), %al
    取日志开关变量的值到寄存器 %al
  • testb %al, %al
    测试 %al(即判断是否启用日志)。
  • je ..B2.14
    如果日志未启用,跳过日志输出代码。
  • 接下来是一系列调用 std::ostream 的输出操作:
    • operator<< 依次输出文件名、行号、消息内容等。
    • 每次调用后 %rax 保存返回的 ostream&,作为下次调用的第一个参数(链式调用)。
    • 最后调用输出流的 endl,刷新输出缓冲。
  • 编译参数包括 -O3(优化),说明生成的汇编是经过高优化的。
  • 总体代码约33条指令,10次函数调用,比较高效。

总结:

  • 通过汇编代码你可以看到,日志开关检查是条件跳转,性能代价低
  • 每条日志输出通过调用标准流操作符实现,保持类型安全和灵活性。
  • 编译器对日志宏生成的代码做了优化,但调用开销仍在(函数调用多)。
  • 汇编代码体现了日志宏背后的底层实现细节。

你指出了日志代码带来的性能和优化问题,总结得很准确:

问题:

  • 日志相关的指令多,尤其是流操作符和条件判断,导致代码臃肿。
  • 这些指令会阻碍编译器的优化,因为它们可能引入不可预测的控制流和函数调用。
  • 影响指令缓存(icache)性能,代码体积变大,缓存命中率降低。
  • 但我们又想保持日志的速度、类型安全和使用方便

目标:

  • 减少调用日志时的指令数量,即代码体积更小。
  • 仍然保持日志输出速度快,不会显著拖慢程序。
  • 保持C++流操作符的类型安全
  • 使用上仍然简洁方便
    这就是日志设计中的经典权衡点。接下来,可以考虑的优化方案包括:
  • 使用**零开销日志(zero-overhead logging)**技巧,编译期剔除无用日志。
  • 设计更轻量的日志API,避免不必要的函数调用。
  • 利用宏和模板元编程在编译期计算是否启用日志。
  • 结合缓冲区或异步日志减少运行时开销。

是一种通过“表达式模板”(Expression Templates)技术解决日志调用开销的方案,核心思路如下:

核心问题

  • 保持 流式(operator<<)接口 的优雅和类型安全
  • 但避免 每次日志调用都产生多次函数调用operator<< 的开销)

解决方案:表达式模板(Expression Templates)

  • 利用 C++ 运算符重载,在编译期把日志表达式 封装成一个类型(表达式树)
  • 日志表达式如 "Read failed: " << file << " (" << error << ")"
    不在运行时一步步调用 operator<<,而是先变成一个编译期的表达式对象
  • 这样可以在运行时 一次性处理整个表达式,减少函数调用次数和指令数量

类比示例

  • 矩阵计算:
    Matrix D = A + B * C;
    编译器用表达式模板避免生成临时矩阵,多做合并优化。
  • 条件查询:
    polygons.Find(VERTICES == 4 && WIDTH >= 20);
    编译时构建查询条件表达式。
  • 日志表达式:
    LOG("Read failed: " << file << " (" << error << ")");
    通过表达式模板,编译时构造表达式树,运行时统一输出。

总结

  • 通过表达式模板,可以实现 零运行时开销的流式日志
  • 保持 类型安全方便易用的接口
  • 大幅减少日志调用时的指令数量,提高性能和优化空间

这是用表达式模板实现日志系统的一部分代码,解析如下:

代码结构

#define LOG(msg) \if (s_bLoggingEnabled) \(Log(__FILE__, __LINE__, LogData<None>() << msg))
template<typename List>
struct LogData {typedef List type;List list;
};
struct None { };

说明

  • LOG(msg) 宏:
    • 检查日志开关 s_bLoggingEnabled
    • 创建一个空的 LogData<None>() 对象(表示空的日志数据列表)
    • 利用重载的 operator<<msg 添加进这个 LogData,构建日志表达式
    • 把文件名、行号和构造的日志数据传给 Log() 函数
  • LogData<List> 模板结构体:
    • 作为表达式模板的核心,保存“日志消息链”(这里用 List 代表消息列表或表达式树)
    • 通过模板递归展开,实现链式拼接日志内容
  • None
    • 表示初始的空日志数据类型

整体作用

  • 利用模板和运算符重载,将日志消息“拼接”成一个类型安全的表达式模板结构
  • 日志内容在运行时才调用 Log() 输出,之前只构建表达式类型,减少多次函数调用开销
  • 保持了流式接口的使用习惯,同时允许编译期优化

这段代码是实现 LogData 表达式模板的关键 operator<<,它实现了日志数据的链式拼接。具体分析如下:

代码内容

template<typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>> operator<<(LogData<Begin>&& begin, Value&& v) noexcept {return {{ std::forward<Begin>(begin.list), std::forward<Value>(v) }};
}

解释

  • 模板参数
    • Begin:表示已有的日志数据类型(表达式模板中的“前半部分”)
    • Value:本次要加入的新日志值类型(右操作数)
  • 参数
    • begin:右值引用,表示已有的 LogData<Begin> 对象(即当前已有的日志链)
    • v:要追加的值(如字符串、变量等)
  • 返回类型
    • 返回一个新的 LogData,其模板参数是一个 std::pair,组合了之前的 begin.list 和新值 v
    • 这样就形成了一个链表结构,每个节点都包含前面的表达式和当前新值
  • 实现
    • 使用 std::forward 完美转发参数,保证传递值的引用性质(左值/右值)
    • 通过花括号初始化 std::pairLogData 对象

作用

  • 每次执行 operator<< 都是把一个新的值追加到已有的 LogData 链表中,形成嵌套的 std::pair 类型链
  • 这个链条在编译时展开,运行时可以一次性遍历输出全部日志内容
  • 保持类型安全无额外函数调用开销

举例

调用示例:

auto data = LogData<None>() << "Error: " << errorCode;
  • 第一次 << "Error:",把 "Error:" 包装进 LogData<std::pair<None, const char*>>
  • 第二次 << errorCode,把 errorCode 和之前链表继续包成新的 LogData<std::pair<std::pair<None, const char*>, int>>
    #结构解析:
LOG("Read failed: " << file << " (" << error << ")");

在宏展开、运算符重载作用下,会构建出如下嵌套类型结构:

LogData<pair<pair<pair<pair<pair<None,char const*>,         // "Read failed: "string const&>,         // filechar const*>,             // " ("int const&>,                // errorchar const*>                  // ")"
>

这是怎么产生的?

每次你使用 <<,都会走这段代码:

template<typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>> operator<<(LogData<Begin>&& begin, Value&& v);

这就把“前一个表达式”和“当前要加入的值”合并为一个新的 std::pair
所以会形成链式嵌套结构,每个新 LogData 都把前一个表达式 Begin 作为链表的头节点。

为什么这样做?

这是表达式模板的经典技巧,优点如下:

优点描述
编译期结构构建表达式在编译期以类型嵌套的形式构建完成,无运行时拼接开销
类型安全所有内容在编译期就明确了类型(string, int 等),无字符串格式化出错问题
无需临时变量不会像 stringstream 那样创建临时对象,减小运行时开销
高性能日志输出编译器可以优化掉未启用日志的所有代码路径(零开销)

总结并详细解释一下你提供的 LOG() 实现和其递归原理:

整体结构

你写的是一个 表达式模板日志系统的运行期输出逻辑,它会:

  1. 在编译期 使用模板构建日志消息的链式结构(嵌套 std::pair
  2. 在运行期 递归展开这个链并输出每一部分

代码详解

Log()

template<typename TLogData>
void Log(const char* file, int line, TLogData&& data) noexcept __attribute__((__noinline__)) {std::cout << file << "(" << line << "): ";Log_Recursive(std::cout, std::forward<typename TLogData::type>(data.list));std::cout << std::endl;
}
  • TLogData 是一个 LogData<...> 类型
  • TLogData::type 是嵌套的 std::pair 类型(日志表达式链)
  • data.list 是实际数据链
  • 使用 Log_Recursive 递归遍历整个链并输出
  • __noinline__:避免编译器内联该函数(保持调试时清晰)

Log_Recursive():递归版本

template<typename TLogDataPair>
void Log_Recursive(std::ostream& os, TLogDataPair&& data) noexcept {Log_Recursive(os, std::forward<typename TLogDataPair::first_type>(data.first));os << std::forward<typename TLogDataPair::second_type>(data.second);
}
  • 遍历 pair<前面内容, 当前值>
  • 先递归打印 .first(链表的前面)
  • 然后输出 .second(当前这一层的值)

Log_Recursive():终止版本

inline void Log_Recursive(std::ostream& os, None) noexcept
{ }
  • 终止条件,当到达最前端 None 类型时不再输出

打印流程图

对于:

LOG("Read failed: " << file << " (" << error << ")");

最终输出类似:

main.cpp(42): Read failed: blah.txt (123)

内部运行时递归调用顺序大致如下:

Log_Recursive(..., pair<..., ")">)└── Log_Recursive(..., pair<..., error>)└── Log_Recursive(..., pair<..., " (">)└── ...└── Log_Recursive(..., None)

每层输出一个值,直到链表遍历完成。

优点总结

优点描述
零运行时开销(禁用时)宏判断 + 模板链式结构避免执行
编译时类型检查所有拼接内容都必须合法 << 运算符
可扩展性强(可加入日志策略)如 mock、profile、日志级别
性能优越优化后无多余函数调用

详细解释一下这段代码的目的和原理,尤其是 处理 std::endl 这样的流操作符(stream manipulators)

问题背景:为什么要特别处理 manipulators?

当你这样写:

LOG("Count: " << count << std::endl);

其中 std::endl 不是普通的值,而是一个 函数指针,它的签名是:

std::ostream& endl(std::ostream&);

这叫做 stream manipulator,像 std::endlstd::flushstd::hex 都是这样。
如果不专门处理它,表达式模板机制就会在编译时出错(类型不匹配)。

解决方案:函数指针偏特化模板

类型定义:

typedef std::ostream& (*PfnManipulator)(std::ostream&);

这就是 manipulator 的类型,本质上是一个函数指针,指向返回 ostream& 并接受一个 ostream& 参数的函数。

operator<< 重载:

template<typename Begin>
LogData<std::pair<Begin&&, PfnManipulator>> operator<<(LogData<Begin>&& begin, PfnManipulator pfn) noexcept {return {{ std::forward<Begin>(begin.list), pfn }};
}
  • Begin 是前面链式结构的日志数据类型
  • pfnstd::endl 这类操作符的函数指针
  • 继续构建嵌套 std::pair

最终效果

这段代码允许如下语法合法且能被正确处理:

LOG("Result is: " << result << std::endl);

Log_Recursive() 函数中,这个 pfn 也能被正确递归处理为:

os << pfn;  // 等效于 os << std::endl;

总结

项目内容
解决的问题支持 std::endl 等流操作符
处理方式ostream& (*)(ostream&) 类型专门提供 operator<< 重载
兼容性完整支持任意流拼接表达式
类型安全编译期确保所有内容都能插入 ostream

这一段代码处理的是 字符串字面量优化(String Literal Optimization)。我们来一步一步拆解并说明这段代码的意图和作用。

问题背景

当你写:

LOG("Error: " << error);

其中的 "Error: " 是一个 字符串字面量(string literal),它的类型是:

const char[8]  // 对于 "Error: " 来说是8(含 null terminator)

这个类型和 const char* 不一样,所以没有合适的 operator<< 重载时会导致模板匹配失败。

解决方案:模板重载接受 const char (&sz)[n]

template<typename Begin, size_t n>
LogData<std::pair<Begin&&, const char*>> operator<<(LogData<Begin>&& begin, const char (&sz)[n]) noexcept {return {{ std::forward<Begin>(begin.list), sz }};
}

参数解释:

  • Begin:前面构建好的链式日志数据结构
  • sz引用类型的字符数组,也就是 string literal,如 "Error"const char (&)[6]
  • n:模板参数,用来匹配任意长度的字符串字面量

返回值:

  • 返回一个新的 LogData,在已有链条的基础上添加一个 const char* 类型
    这就将 "Error" 类型从 const char[6] 转换成了 const char* 存储,更加轻量,也统一了类型。

优势

优点描述
支持字符串字面量不支持会导致编译错误
避免每次都拷贝字符串字面量的内容只存指针,效率高
统一类型为 const char*简化后续递归处理逻辑
零开销类型转换编译期完成,不增加运行时负担

示例用法

LOG("Error at line: " << lineNum << " in file: " << filename);

对于上面这段,模板重载会自动识别 "Error at line: "" in file: " 为 string literal,匹配这个特化模板,转成 const char*

总结

你看到的这一段代码实现的是:

专门支持 "字符串字面量" 这种特殊的数组类型,并将其优化为 const char* 来存储,提高日志系统的灵活性和效率。

你这段汇编和其后的模板展开反映的是 高性能、低指令开销的日志系统实现方式

场景回顾

你在使用这样的日志语句:

LOG("Read failed: " << file << " (" << error << ")");

其中 LOG 是一个宏,最终展开为对 Log(...) 的一次调用,使用了 表达式模板(expression templates) 技术来延迟表达式求值、减少运行时指令。

汇编代码解释

movb g_bLogging(%rip), %al      ; 加载全局布尔变量 s_bLoggingEnabled
testb %al, %al                  ; 测试它是否为真
je    ..B6.7                    ; 如果为假,跳转(不执行日志)
movb $0, (%rsp)                 ; 临时栈处理(非关键)
movl $.L_2__STRING.4, %ecx      ; 加载字符串字面量指针(如 " (" )
movl $.L_2__STRING.3, %edi      ; 加载另一个字符串(如 "Read failed: ")
movl $40, %esi                  ; 加载行号
lea   128(%rsp), %r9            ; 设置临时地址作为参数
call Log<...>                   ; 调用唯一的 Log 模板函数实例

模板展开(推导出的 LogData)

Log<pair<pair<pair<pair<pair<None,char const*       // "Read failed: ">,string const&         // file>,char const*               // " (">,int const&                   // error>,char const*                     // ")"
>>

这是你通过 << 运算符链式拼接出来的日志数据表达式,模板在编译期就构建好了这些类型。
最终传给唯一的 Log(...) 函数,运行时只需一次函数调用(pimp’d function call)即可完成整条日志输出。

优点

优点说明
编译期表达式组合<< 构建表达式的结构体,不执行实际操作
运行期延迟调用只有一次 Log 函数调用(模板实例化)
少量汇编指令这里只用了 9 条汇编指令来构造参数和调用
保留语义完整性LOG(...) 保持流式语法,且类型安全
优化友好编译器能轻松做内联或省略,性能极高

理解重点

你看到的这一切说明:表达式模板技术允许你写出非常干净的代码,而编译器又能生成非常高效的汇编。 它兼顾了:

  • 语法简洁
  • 类型安全
  • 运行时性能
  • 最小的指令和函数调用开销

总结提到的是:使用表达式模板的日志系统,在性能和编译优化方面的巨大优势。下面逐点解释:

SUMMARY 理解

• Expression templates solution

表达式模板方案
使用表达式模板技术(即 << 拼接被延迟到编译期生成类型结构),实现日志消息的构建。这种方式不在日志调用处做字符串拼接或格式化,而是传递一个类型安全的结构(LogData<...>)到一个统一的日志处理函数。

• Reduced instructions at call site by 73% (33 → 9)

调用处的汇编指令减少了 73%(从 33 条降到 9 条)
传统的日志实现使用大量的 operator<<,每次 << 都是函数调用。表达式模板把这些函数调用“挪”到编译期,只留下最终调用 Log(...) 的一次函数调用。
这极大地提升了性能,特别是在嵌入式或高频调用场景下。

• Mo’ args, mo’ savings

参数越多,节省越大
传统日志系统每多一个参数,就多一到两个函数调用。而表达式模板只构建更复杂的类型结构,运行时开销不变。你写:

LOG("Read failed: " << file << " (" << error << ") at offset " << offset << ", reason: " << reason);

无论参数多少,运行时也就一两次函数调用。结果是:
参数越多,节省越多!

总结一句话

表达式模板让你保留了优雅的写法,却几乎不付出运行时代价,是一种兼顾语义表达极致性能的高级技巧。

可变参数模板(Variadic Template)日志系统 的完整代码版本,并附带了详细注释,帮助你理解其结构与作用:

完整代码:Variadic Template Logging

#include <iostream>
#include <string>
bool s_bLoggingEnabled = true;  // 控制是否启用日志
// 提前声明递归函数模板
template <typename T, typename... Args>
void Log_Recursive(const char* file, int line, std::ostream& os, T first, const Args&... rest);
inline void Log_Recursive(const char* file, int line, std::ostream& os);  // 终止版本声明
// 定义 LOG 宏,用于自动捕获 __FILE__ 和 __LINE__,并转发可变参数到 Log_Variadic
#define LOG(...) Log_Variadic(__FILE__, __LINE__, __VA_ARGS__)
// 主日志函数模板,接受任意数量和类型的参数
template <typename... Args>
void Log_Variadic(const char* file, int line, const Args&... args) {if (!s_bLoggingEnabled) return;                 // 如果日志未启用,直接返回std::cout << file << "(" << line << "): ";      // 打印文件名和行号Log_Recursive(file, line, std::cout, args...);  // 展开参数并输出std::cout << std::endl;                         // 换行
}
// 递归模板函数:处理一个参数,然后递归处理剩余的参数
template <typename T, typename... Args>
void Log_Recursive(const char* file, int line, std::ostream& os, T first, const Args&... rest) {os << first;                             // 打印当前参数Log_Recursive(file, line, os, rest...);  // 递归处理剩余参数
}
// 递归终止函数:不做任何事(没有参数时)
inline void Log_Recursive(const char* file, int line, std::ostream& os) {// 什么都不做,递归终止
}

使用示例

// 示例
int main() {std::string file = "config.txt";int errorCode = 404;LOG("Failed to read file: ", file, ", error code: ", errorCode);
}

输出示例:

/home/xiaqiu/test/CppCon/day46/code/main.cpp(40): Failed to read file: config.txt, error code: 404

特点总结

特性说明
类型安全使用模板展开参数,支持任意类型(只要可输出到 std::ostream
语法简洁printf 相比更现代、更安全,且无需格式字符串
可扩展可添加如时间戳、线程 ID、颜色等功能
条件编译可以结合宏或 s_bLoggingEnabled 控制开关

下面这段代码(专门用于优化字符串字面量处理)的完整可运行示例,包含基础 LogData 类型、递归输出逻辑、宏定义等内容。这个版本模拟了使用 表达式模板风格的日志系统,特别是你提到的 operator<< 针对 const char[N] 的特化处理。

完整示例代码:支持字符串字面量优化的日志系统

#include <iostream>
#include <string>
#include <utility>
#include <type_traits>
// 控制是否启用日志
bool s_bLoggingEnabled = true;
// 基础类型 None,用于终止递归链表结构
struct None { };
// 表达式模板用的包装类型 LogData
template<typename List>
struct LogData {using type = List;List list;
};
// 重载 operator<<:通用版本,将任意类型“连接”到 LogData 链中
template<typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>>
operator<<(LogData<Begin>&& begin, Value&& v) noexcept {return {{ std::forward<Begin>(begin.list), std::forward<Value>(v) }};
}
// 特化版本:优化 const char[N] 字符串字面量的拼接(避免构造 std::string)
template<typename Begin, size_t n>
LogData<std::pair<Begin&&, const char*>>
operator<<(LogData<Begin>&& begin, const char (&sz)[n]) noexcept {return {{ std::forward<Begin>(begin.list), sz }};
}
// 操作流控制符(如 std::endl)的版本
using PfnManipulator = std::ostream& (*)(std::ostream&);
template<typename Begin>
LogData<std::pair<Begin&&, PfnManipulator>>
operator<<(LogData<Begin>&& begin, PfnManipulator pfn) noexcept {return {{ std::forward<Begin>(begin.list), pfn }};
}
// 宏:包装日志调用,自动注入文件名和行号
#define LOG(msg) \if (s_bLoggingEnabled) \Log(__FILE__, __LINE__, LogData<None>() << msg)
// 主日志函数(展开表达式链)
template<typename TLogData>
void Log(const char* file, int line, TLogData&& data) noexcept {std::cout << file << "(" << line << "): ";Log_Recursive(std::cout, std::forward<typename TLogData::type>(data.list));std::cout << std::endl;
}
// 递归打印表达式链
template<typename TLogDataPair>
void Log_Recursive(std::ostream& os, TLogDataPair&& data) noexcept {Log_Recursive(os, std::forward<typename TLogDataPair::first_type>(data.first));os << std::forward<typename TLogDataPair::second_type>(data.second);
}
// 递归终止函数
inline void Log_Recursive(std::ostream& os, None) noexcept {// Do nothing
}
// 示例
int main() {std::string filename = "data.txt";int errCode = 42;LOG("Error opening " << filename << ": code " << errCode << std::endl);
}

输出示例

如果编译并运行,会输出类似:

main.cpp(87): Error opening data.txt: code 42

特点说明

  • 表达式模板 LogData + operator<< 允许将多个元素拼接成“延迟求值”的链表结构;
  • 字符串字面量版本的 operator<< 避免将其视为模板推导中需要额外构造的 std::string,提升效率;
  • 支持任意类型拼接、流操作符(如 std::endl);
  • 所有输出逻辑最终只在 Log() 函数中统一处理,便于拦截、替换输出流(比如写入文件)或增加 profiling。

http://www.hkcw.cn/article/GGMuRlkvqZ.shtml

相关文章

LabVIEW磁悬浮轴承传感器故障识别

针对工业高端装备中主动磁悬浮轴承&#xff08;AMB&#xff09;的位移传感器故障检测需求&#xff0c;基于 LabVIEW 平台构建了一套高精度故障识别系统。通过集成品牌硬件与 LabVIEW 的信号处理能力&#xff0c;实现了传感器探头故障的实时监测与精准定位&#xff0c;解决了传统…

Qt开发:QThreadPool的介绍和使用

文章目录 一、QThreadPool 简介二、常用函数简介三、完整示例 一、QThreadPool 简介 QThreadPool 是 Qt 提供的用于高效管理线程资源的类。它通过线程池的方式管理和复用线程&#xff0c;适合处理大量、短时间运行的任务&#xff0c;避免频繁创建和销毁线程带来的性能开销。 常…

蚂蚁感冒--思维

1.相遇后不用考虑转头&#xff0c;继续走就可以 2.思维&#xff0c;不只是傻傻的模拟&#xff0c;要总结出规律&#xff0c;什么情况一定可以感染&#xff0c;然后感染之后再怎么这么样 P8611 [蓝桥杯 2014 省 AB] 蚂蚁感冒 - 洛谷 #include<bits/stdc.h> using names…

non-autoregressive sequence generation

非自回归non-autoregressive 传统rnn是autoregressive,而且encode和decode都是根据上一个input/output,这样花费的时间就和句子长度成正比 transformer的输入是并行的,但是decode阶段还是autoregressive 单纯把影像当成 NM 个独立像素去拟合&#xff0c;会缺乏像素之间的依赖…

实验设计与分析(第6版,Montgomery著,傅珏生译) 第10章拟合回归模型10.9节思考题10.1 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第10章拟合回归模型10.9节思考题10.1 R语言解题。主要涉及线性回归、回归的显著性、回归系数的置信区间。 vial <- seq(1, 10, 1) Viscosity <- c(160,171,175,182,184,181,188,19…

如何选择最高效的沟通方式?

日常沟通主要分为文字、语音和面对面三种形式&#xff0c;选择何种方式需根据沟通内容的复杂程度、决策难度及互动需求综合判断。 当沟通内容简单明确、以信息传递为主或涉及基础决策时&#xff0c;文字或语音是更高效的选择。这类方式不仅能降低时间成本&#xff0c;还能避免…

VueScan:全能扫描,高清输出

在数字化办公和图像处理的领域&#xff0c;扫描仪扮演着不可或缺的角色。无论是文档的数字化存档、照片的高清复制&#xff0c;还是创意项目的素材采集&#xff0c;一款性能卓越、操作便捷的扫描软件能大幅提升工作效率和成果质量。VueScan正是这样一款集多功能于一身的扫描仪软…

【Hot 100】279. 完全平方数

目录 引言完全平方数我的解题dp总结 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;【Hot 100】279. 完全平方数❣️ 寄语&#xff1a;书到用时方恨少&#xff0c;事非经过不知难&#xff01; 引言 今天又…

Alita:通过 MCP 实现自主进化的通用 AI 代理

Alita 是一个创新的通用 AI 代理&#xff0c;采用极简主义设计哲学&#xff0c;强调 minimal predefinition&#xff08;最小预定义&#xff09;和 maximal self-evolution&#xff08;最大自主进化&#xff09;。通过利用 Model Context Protocols (MCPs)&#xff0c;Alita 能…

关于物联网的基础知识(二)——物联网体系结构分层

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于物联网的基础知识&#xff08;二&a…

大语言模型评测体系全解析(上篇):基础框架与综合评测平台

文章目录 一、评测体系的历史演进与技术底座&#xff08;一&#xff09;发展历程&#xff1a;从单任务到全维度评测1. 2018年前&#xff1a;单数据集时代的萌芽2. 2019-2023年&#xff1a;多任务基准的爆发式增长3. 2024年至今&#xff1a;动态化、场景化、多模态体系成型关键节…

SpringAI系列 - MCP篇(三) - MCP Client Boot Starter

目录 一、Spring AI Mcp集成二、Spring AI MCP Client Stater三、spring-ai-starter-mcp-client-webflux集成示例3.1 maven依赖3.2 配置说明3.3 集成Tools四、通过SSE连接MCP Server五、通过STDIO连接MCP Server六、通过JSON文件配置STDIO连接一、Spring AI Mcp集成 Spring AI…

MyBatis 一级缓存与二级缓存

一、缓存概述 MyBatis 提供两级缓存机制提升查询性能&#xff1a; 一级缓存&#xff1a;SqlSession 级别&#xff0c;默认开启 二级缓存&#xff1a;Mapper 级别&#xff0c;需手动开启 两者协同工作&#xff0c;形成查询数据优先级&#xff1a;二级缓存 → 一级缓存 → 数据…

008房屋租赁系统技术揭秘:构建智能租赁服务生态

房屋租赁系统技术揭秘&#xff1a;构建智能租赁服务生态 在房地产租赁市场日益活跃的当下&#xff0c;房屋租赁系统成为连接房东与租客的重要数字化桥梁。该系统集成用户管理、房屋信息等多个核心模块&#xff0c;面向管理员、房东和用户三类角色&#xff0c;通过前台展示与后…

HTTP协议完全指南:从请求响应到HTTPS安全机制

文章目录 一、HTTP协议中的基本概念1.HTTP协议介绍&#xff08;1&#xff09;协议&#xff08;2&#xff09;传输&#xff08;3&#xff09;超文本 2.统一资源定位符&#xff08;URL&#xff09; 二、HTTP协议中的请求和响应1.HTTP客户端请求消息&#xff08;1&#xff09;请求…

第11节 Node.js 模块系统

为了让Node.js的文件可以相互调用&#xff0c;Node.js提供了一个简单的模块系统。 模块是Node.js 应用程序的基本组成部分&#xff0c;文件和模块是一一对应的。换言之&#xff0c;一个 Node.js 文件就是一个模块&#xff0c;这个文件可能是JavaScript 代码、JSON 或者编译过的…

『uniapp』把接口的内容下载为txt本地保存 / 读取本地保存的txt文件内容(详细图文注释)

目录 预览效果思路分析downloadTxt 方法readTxt 方法 完整代码总结 欢迎关注 『uniapp』 专栏&#xff0c;持续更新中 欢迎关注 『uniapp』 专栏&#xff0c;持续更新中 预览效果 思路分析 downloadTxt 方法 该方法主要完成两个任务&#xff1a; 下载 txt 文件&#xff1a;通…

XCTF-web-ics-05

看一下有什么 只有/index.php 模糊测试得到一个page ┌──(kali㉿kali)-[~] └─$ ffuf -u "http://223.112.5.141:52073/index.php?FUZZFUZZ" -w /usr/share/wordlists/rockyou.txt -fc 403 -c -fs 2305 -s page尝试用php伪协议读取源码?pagephp://filter/readc…

Redis线程模型

前面的文章介绍了Redis的底层数据结构&#xff0c;这篇文章来介绍一下Redis的线程模型。 Redis为什么选择单线程&#xff1f; 官方的回答是这样的&#xff0c;对于Redis来说&#xff0c;CPU通常不会成为瓶颈&#xff0c;因为大多数的请求不会是CPU密集型的&#xff0c;而是IO密…

工厂方法模式深度解析:从原理到应用实战

作者简介 我是摘星&#xff0c;一名全栈开发者&#xff0c;专注 Java后端开发、AI工程化 与 云计算架构 领域&#xff0c;擅长Python技术栈。热衷于探索前沿技术&#xff0c;包括大模型应用、云原生解决方案及自动化工具开发。日常深耕技术实践&#xff0c;乐于分享实战经验与…