【问题标题】:C++ iostream Corruption using stringstream使用字符串流的 C++ iostream 损坏
【发布时间】:2012-04-11 16:28:35
【问题描述】:

我正在尝试编写一个非常简单的线程安全记录器。理想情况下,我希望它像std::cout 一样工作,您可以重载<< 运算符,然后让所有内容神奇地显示在日志中。我在 Windows 机器上,所以这是我尝试过的方法:

// Threadsafe logger
class Logger
{
public:
  Logger()
  {
    InitializeCriticalSection(&s);
  }

  ~Logger()
  {
    DeleteCriticalSection(&s);
  }

  void Log(std::ostream const& os)
  {
    EnterCriticalSection(&s);
    //std::cout << static_cast<std::stringstream const&>(os).str();
    std::cout << os.rdbuf();
    LeaveCriticalSection(&s);
  }

private:
  CRITICAL_SECTION s;
};

请注意,我对Log() 函数尝试了两种方法。我接受ostream 的原因是因为这就是stringstream 在调用&lt;&lt; 运算符后似乎产生的结果。当我运行此代码时,Log() 函数的两种变体都以相同的方式失败:

#include <iostream>
#include <sstream>
#include <Windows.h>

int main(int argc, char* argv[])
{
  Logger logger;
  //logger.Log(std::stringstream("Test"));
  logger.Log(std::stringstream("Another ") << "test");
  std::cin.get();
}

使用 Log 函数的两种变体输出第一行(“测试”)可以正常工作并正确显示。第二行输出一个错位的输出:

testher

这显然是test 覆盖Another。我对这些流的工作方式缺少什么?我尝试拨打flush 电话希望能解决问题,但它什么也没做。

我如何才能在线程安全的记录器上尝试与流一起正常工作?

【问题讨论】:

  • 你试过 logger.Log((std::stringstream("Another")
  • 我刚刚尝试过,它产生了相同的结果。感谢您的尝试!我认为应该是一样的,因为在调用Log() 函数之前,应该对所有函数参数进行全面评估。
  • 我赞成 edransch 的回答,因为它描述了为什么你会得到这个结果,但 Jerry Coffin 的回答指出了一个不相关的严重问题。我可能会使用标准的 C++ 构造(例如,&lt;atomic&gt;&lt;mutex&gt;&lt;thread&gt;)来解决它,因为它们已经拥有 C++ 所需的适当 RAII 语义。
  • 或者只使用 fprintf,因为它已经是线程安全的:fprintf(stdout,"%s\n",(std::stringstream() &lt;&lt; "another" &lt;&lt; " test").str().c_str());
  • 同意——杰里的回答超越了我的整个方法,但 edransch 直接回答了问题的核心。我没有意识到 fprintf 是线程安全的——这是一个非常好的单行解决方案!

标签: c++ multithreading thread-safety iostream


【解决方案1】:

使用可变参数模板:

void Log_impl(std::ostream &os) {} // recursion base case

template<typename T,typename... Us>
void Log_impl(std::ostream &os,T &&t,Us &&... us) {
    os << std::forward<T>(t);
    Log_impl(os,std::forward<Us>(us)...);
}

template<typename... Ts> void Log(Ts &&... ts) {
    std::stringstream ss;
    Log_impl(ss,std::forward<Ts>(ts)...);
    fprintf(stdout,"%s\n",ss.str().c_str()); // thread safe output
}

用法:

Log("Another"," test ",100);

我还没有实际测试过这段代码...

【讨论】:

    【解决方案2】:

    至少在我看来,这种解决问题的方法至少使用起来有些笨拙,因为它需要您创建某种辅助ostream 对象,将数据流式传输到其中,然后将其传递到您的日志.这似乎与你所说的你真正喜欢的不太吻合。

    对于您如何完成线程锁定代码,我也有点不高兴。举个例子,如果您在std::cout &lt;&lt; os.rdbuf(); 期间遇到异常,您可以退出范围而不离开临界区。

    我想我会从关键部分的薄包装开始,添加一个 RAII 样式的类来锁定关键部分(并在超出范围时自动解锁它),然后在实现 @987654323 时使用它们@ 班级。此外,我会作弊并让Log 类使用模板成员函数一举接受几乎任何类型的输出:

    编辑:经过深思熟虑,我决定接受这样一种观念,即每个问题都可以通过另一个间接级别来解决。为此,我添加了一个中间 transaction,它封装了将多个项目的输出链接到 stringstream,然后将该结果作为线程安全事务写出。

    #include <windows.h>
    #include <iostream>
    #include <sstream>
    
    class crit_sect {
        CRITICAL_SECTION cs;
    
        void lock() { EnterCriticalSection(&cs); }
        void unlock() { LeaveCriticalSection(&cs); }
        friend class lock;
    
        crit_sect(crit_sect const &); /* = delete; */
        crit_sect &operator=(crit_sect const &other); /* = delete; */
    public:
        crit_sect() { InitializeCriticalSection(&cs); }
        ~crit_sect() { DeleteCriticalSection(&cs); }
    };
    
    class lock {
        crit_sect &cs;
    public:
        lock(crit_sect &c) : cs(c) { cs.lock(); }
        ~lock() { cs.unlock(); }
    };
    
    class transaction {
        std::ostringstream buffer;
    public:
        transaction(std::string const &s="") : buffer(s, std::ios::out | std::ios::ate) {}
    
        template <class T>
        transaction &operator<<(T const &t) {
            buffer << t;
            return *this;
        }
    
        friend std::ostream &operator<<(std::ostream &os, transaction const &t) {
            return os << t.buffer.str();
        }
    };
    
    class Log {
        std::ostream &out;
        crit_sect mutex;
    public:
        Log(std::ostream &sink) : out(sink) { }
    
        template <class T>
        void operator<<(T const &t) {
            lock l(mutex);
            out << t;
        }    
    };
    
    int main() {
        Log l(std::cout);
    
        l << "This is a string\n";
    
        l << (transaction("Another ") << "Test");
        return 0;
    }
    

    由于log 类型不支持链接,任何不使用transaction 链接输出的尝试都会失败(不会编译)。与原版相比,使用仍然更简洁——ostringstream ctor 所需的额外参数被隐藏,transaction 的名称明确了正在做什么,或者更确切地说,完成了。

    【讨论】:

    • 澄清一下,在这个实现中,你必须确保不要用链式输出调用l,因为那样你就会失去线程安全,对吧?我认为这就是你试图用那里的缓冲区展示的东西......
    • @aardvarkk:是的,不幸的是这是真的。然而,澄清一下,你并没有真正失去任何东西——你只是没有添加一些东西(虽然,诚然,拥有一些东西真的很好)。
    • 这只是让我有点担心,在使用记录器时您仍然必须认识到这一事实,否则它实际上不再是线程安全的记录器(至少在我想象的意义上)。但是我想我想象的简单实现是不可能的,因为在多线程世界中,您永远不会知道日志记录何时开始和停止。作为记录,我认为你的回答很棒而且很有帮助!我确实从中学到了一些东西(我现在对 RAII 的理解稍微一点了!)。不幸的是,我觉得 edransch 更直接地解决了我遇到的问题。
    • @aardvarkk:再想一想,最好禁用这个类的链接,这样用户就不会意外使用它。
    • 这很有意义。感谢创建如此可靠的日志记录解决方案!
    【解决方案3】:

    问题不在于记录器,而在于您对 stringstream 的使用。 初始化 std::stringstream 时,流的位置指示符位于流的开头。

    现在,当您开始使用 '

    要解决这个问题,您可以使用 std::stringstream("另一个", stringstream::in | stringstream::out | std::stringstream::ate)

    (根据http://www.cplusplus.com/reference/iostream/stringstream/stringstream/

    【讨论】:

      【解决方案4】:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多