【问题标题】:How can I replace `{ lock_guard lock(obj.mut); obj.a_stringstream << a << b /*...*/ << n;}` with a method call如何替换 `{ lock_guard lock(obj.mut); obj.a_stringstream << a << b /*...*/ << n;}` 带有方法调用
【发布时间】:2016-07-06 16:52:09
【问题描述】:

我有一个struct 喜欢

struct log {
    boost::mutex mut;
    std::stringstream a_stringstream;
    //...
    };

在我的代码中,我有一个向量并且经常这样做

{
    boost::lock_guard <boost::mutex> lock(logs[AN_ENUM].mut);
    logs[AN_ENUM].a_stringstream << "something" << a_double << a_string << etc;
}

每次我想记录一些东西时,都有锁保护和括号似乎很冗长。起初我想也许我可以为我的结构重载 &lt;&lt; 运算符,但我读过它,我认为这行不通。有什么好方法可以将其压缩为方法调用吗?

【问题讨论】:

  • 一个典型的设置是使用logs[AN_ENUM] &lt;&lt; a &lt;&lt; b &lt;&lt; c;,其中第一个&lt;&lt;返回一个包含字符串流的对象,该对象采用&lt;&lt;,其析构函数完成所有工作。但是,使用 Howard 建议的函数调用语法的一个优点是,您可以实现日志级别,然后如果此消息低于当前级别,则不会在运行时浪费时间。
  • @M.M 日志级别?而且我想不出析构函数会做什么。如果您愿意,最好同时拥有这两种解决方案。
  • “日志级别”意味着您可以为日志消息分配不同的优先级,然后在运行时(例如响应配置文件)决定显示或不显示什么。例如,您可能会记录对调试有用的信息,但您通常不想记录这些信息,除非有人遇到问题并决定打开它。
  • @MM 啊。而使用#ifdef 当然只是编译时的。说得通。虽然,流确实接受参数,例如std::fixed 通过 &lt;&lt; 运算符?
  • 是的。顺便说一句,这是使用我建议的设置的另一个原因;你现在拥有它的方式,标志将是“粘性的”,例如您的 fixed 将一直存在直到它被取消设置,这可能会与其他日志消息混淆

标签: c++ oop methods


【解决方案1】:

您可以创建一个可变参数模板函数(例如log_it),该函数接受AN_ENUM 加上可变数量的记录,锁定mutex 一次,然后将所有内容流式传输到a_stringstream。例如:

#include <mutex>
#include <sstream>
#include <vector>

class save_stream
{
    std::ostream&      os_;
    char               fill_;
    std::ios::fmtflags flags_;
    std::streamsize    precision_;

public:
    ~save_stream()
    {
        os_.fill(fill_);
        os_.flags(flags_);
        os_.precision(precision_);
    }

    save_stream(const save_stream&) = delete;
    save_stream& operator=(const save_stream&) = delete;

    explicit save_stream(std::ostream& os)
        : os_(os)
        , fill_(os.fill())
        , flags_(os.flags())
        , precision_(os.precision())
        {}
};

struct log
{
    std::mutex mut;
    std::stringstream a_stringstream;
};

std::vector<log> logs(10);

enum : std::size_t {AN_ENUM};

void
log_one(std::size_t)
{
}

template <class Arg0, class ...Args>
void
log_one(std::size_t log, Arg0 const& arg0, Args const& ...args)
{
    logs[log].a_stringstream << arg0;
    log_one(log, args...);
}

template <class ...Args>
void
log_it(std::size_t log, Args const& ...args)
{
    std::lock_guard<std::mutex> lock(logs[log].mut);
    save_stream s{logs[log].a_stringstream};
    logs[log].a_stringstream << "log " << log << " says : ";
    log_one(log, args...);
    logs[log].a_stringstream << '\n';
}

这不是大量的代码。 log_it 接受一个整数常量,使用它来索引logs 以锁定互斥锁,吐出前缀字符串,然后使用常量日志索引和可变数量的参数调用log_one 以注销。

log_one 然后简单地记录第一个参数,然后在包的其余部分递归调用 log_one

可以这样使用:

log_it(AN_ENUM, std::fixed, std::setprecision(3), "i = ", 4.5);
log_it(AN_ENUM,                                   "j = ", 4.5);

导致logs[0].a_stringstream 持有:

log 0 says : i = 4.500
log 0 says : j = 4.5

我确信语法可以被巧妙地改进为更漂亮的东西,但这可以理解基本思想:传递可变数量的 args,锁定在调用堆栈的顶部,然后一个一个地处理每个 args。

“日志级别”可能只是log_it 的另一个参数。

【讨论】:

    【解决方案2】:

    我想不出这个模板重载

    template<typename param>
    auto operator<<(param &&Param)
    {
        boost::lock_guard <boost::mutex> lock(mut);
    
        a_stringstream << std::forward<param>(Param);
    
        return *this;
    }
    

    我不知道您阅读的内容可能表明这种方法行不通,但无论您阅读什么,它都是错误的。我的示例可能需要在这里和那里进行一些调整,具体取决于您的实际课程,但理论上它应该是非常可行的。

    【讨论】:

    • 这将释放每个输出值之间的锁定,可能允许另一个线程在应该是连续输出之间输出一些东西(例如,在 OP 的示例中,在a_doublea_string 之间)。跨度>
    • 你仍然可以这样做,但你必须做更多的工作。基本上:让这个模板实例化一个包含锁保护的 shared_ptr 和一个指向它的指针。让此模板返回 shared_ptr,并将初始 operator
    • ...继续。如果您的应用程序记录大量日志,但仍然可行,则更难,并且性能可能会很差。您是否考虑过不使用运算符
    • 很高兴被提醒 C++ 有多么复杂,还有多少我还不知道。我查看了一个示例,出于某种原因假设该参数必须与operator&lt;&lt; 的返回值具有相同的类型。 std::forward 的描述令人困惑,我不明白为什么它是必要的。幸运的是,就我而言,唯一可能发生争用的时间是在写入磁盘时定期调用交换字符串流,所以你的基本版本应该适合我。
    猜你喜欢
    • 1970-01-01
    • 2013-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多