如何在 C++ 中的多个位置访问 class 的模板化静态变量

How to access templatized static variable of a class in multiple places in c++

我正在使用 C++ 开发功能模拟器项目。我的目标是将最后 50 条调试日志语句存储在滚动缓冲区中,并在模拟器出现错误时显示它们。这将有助于更快地进行调试。否则我将不得不在出现错误时记下周期并重新运行启用日志的模拟器,这既耗时又缓慢。

在文件中abc.h 日志结构存储调试日志语句、生成日志的模拟周期等

template <typename T, typename... U>
struct Logging {
    ......
}

abc.h 中的以下模板化 class 包含一个静态成员,它是滚动的 window 包含最后 50 个日志调试语句

template <typename T> 
class LogGlobal {
   public:    
       static std::deque<T> last_50_logs; //rolling window of last 50 log statements

};
template <typename T> std::deque<T> LogGlobal<T>::last_50_logs = {};

在 debug_log 函数中 abc.h 我将一些对象推送到静态变量,如下所示:

template <typename T, typename... U>
void debug_log(T&& t, U&&... u) const
{
    //other code to ensure that this deque holds last 50 log statements only is not written here for simplicity
    LogGlobal<Logging<T, U...>>::last_50_logs.push_back(<Logging<T, U...>{...}); 
}

在 abc.cpp 文件中,我们有一个错误处理函数,只要模拟器出现错误就会调用该函数。

void error_handler()
{
    //I want to access the last_50_logs static variable here but I don't have information about the template T and U here. How do I solve this?
} 

在 error_handler 函数中,我无法访问用于创建 LogGlobal class 的模板类型。有没有办法或解决方法来了解这些详细信息?

解决这个问题的一种方法是每当我推送到 last_50_logs 时,我都可以将日志语句存储为 std::string 而不是日志结构中的模板 T 和 U。但这真的很慢,因为字符串操作很昂贵。

每个日志消息的不同类型,就像在您的示例中一样,太麻烦且不方便实用。

日志通常是文本,因此自然的解决方案是为您的日志消息使用 std::string 的循环缓冲区。您将消息格式化为 std::string 并将其存储到缓冲区中。

对于结构化日志记录,您的日志记录必须有另一个特定的(基础)class。


如果您不想在消息输出之前对其进行格式化,请使用格式化函数的环形缓冲区来复制日志消息参数。

每个格式函数都是一个 lambda 闭包对象,具有按值捕获的日志消息参数。按价值至少是为了防止引用变得悬空。您仍然可以传递一个稍后可能会变成悬空的指针,但可以在编译时拒绝指针参数(除非它们指向编译时常量,具有非标准编译器扩展。)。

一个完整的例子:

#include <boost/circular_buffer.hpp>
#include <iostream>
#include <functional>

struct LogHistory {
    using FormatFn = std::function<void(std::ostream&)>;
    boost::circular_buffer<FormatFn> last_10_logs_{10};

    template<class... T>
    void log(T&&... args) {
        last_10_logs_.push_back([=](std::ostream& s) { (s << ... << args); });
    }
};

int main() {
    LogHistory l;
    for(int i = 0; i < 20; ++i)
        l.log("hello ", i);

    for(auto const& f : l.last_10_logs_) {
        f(std::cout);
        std::cout << '\n';
    }
}

输出:

hello 10
hello 11
hello 12
hello 13
hello 14
hello 15
hello 16
hello 17
hello 18
hello 19

如果您想向用户显示这些数据,您迟早还是需要将其字符串化(大多数记录器都是这样工作的)。但是,如果需要始终将其保留为二进制数据(假设您收集的数据稍后会转储到文件中并可以按需解码),那么您可以查看 std::any,它在运行时存储类型数据,但是此类修改需要对您的代码进行更大的更改。

One way to solve this problem is whenever I push to last_50_logs, I can store log statements as std::string instead of template T and U inside Logging structure. But this is really really slow as string operations are expensive.

嗯,您不能将日志事件存储为某种未知类型 T,因为...那样您就不知道该类型是什么了。

但是,您可以使用某种类型擦除来推迟格式化,直到(除非)您确实需要该字符串。例如,您可以在双端队列中存储一个 std::function<std::string()> 对象,

using DeferredLog = std::function<std::string()>;
class LogGlobal {
   public:    
       static std::deque<DeferredLog> last_50_logs; //rolling window of last 50 log statements
};

template <typename T, typename... U>
void debug_log(T&& t, U&&... u) const
{
    // blah blah blah circular buffer
    LogGlobal<Logging<T, U...>>::last_50_logs.push_back(
      [t, u...] () { return format_log(t, u...); }
    ); 
}

请注意,类型擦除确实有一些开销,并且所有参数都需要按值捕获,因此不能保证比仅格式化固定宽度的字符串更快。对其进行基准测试并查看。如果看起来值得,您总是可以使用 lambda 捕获来手动滚动比 std::function 更不通用的东西。