C++ iostream Corruption using stringstream

浪尽此生 提交于 2019-12-17 17:10:28

问题


I'm trying to write a really simple thread-safe logger. Ideally, I wanted it to work just like std::cout, wherein you could just overload the << operator and have everything magically show up in the log. I'm on a Windows machine, so here's the approach I tried:

// 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;
};

Notice that I've tried two approaches to the Log() function. The reason I accept an ostream is because that's what a stringstream seems to produce after the << operator is called. Both variants of the Log() function fail in the same way when I run this code:

#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();
}

Outputting the first line ("Test") works correctly and displays properly using both variants of the Log function. The second line outputs a mangled output:

testher

which is obviously test written over Another. What am I missing about the way these streams work? I tried making a flush call hoping that would fix things, but it did nothing.

How can I get this attempt at a thread-safe logger working correctly with the streams?


回答1:


The problem isn't with the logger, rather it's with your use of stringstream. When std::stringstream is initialized, the stream's position indicator is positioned at the beginning of the stream.

Now when you start writing to the string using '<<', you start writing at the position indicator, replacing whatever was there before.

To work around this, you can initialize the stringstream with std::stringstream("Another ", stringstream::in | stringstream::out | std::stringstream::ate)

(as per http://www.cplusplus.com/reference/iostream/stringstream/stringstream/ )




回答2:


Using variadic templates:

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
}

usage:

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

I haven't actually tested this code...




回答3:


At least in my opinion, this approach to the problem is at least somewhat clumsy to use, since it requires you to create an auxiliary ostream object of some sort, stream data into it, and then pass that to your log. That doesn't seem to fit very closely with what you've said you'd really prefer.

I'm also a bit less than excited about how you've done your thread-locking code. Just for example, if you get an exception during your std::cout << os.rdbuf();, you can exit the scope without leaving the critical section.

I think I'd start with a thin wrapper around a critical section, add an RAII-style class to lock the critical section (and automatically unlock it when it goes out of scope), then use those in implementing the Log class. Further, I'd cheat and have the Log class use a template member function to accept almost any type of output in one fell swoop:

Edit: After some more thought, I've decided to embrace the notion that every problem can be solved with another level of indirection. To do that, I've added an intermediate transaction that encapsulates chaining the output of a number of items into a stringstream, then writing out that result as a thread-safe transaction.

#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;
}

Since the log type doesn't support chaining, any attempt at chaining the output without using a transaction will fail (won't compile). Compared to the original, usage is still a bit cleaner though -- the extra parameters needed for the ostringstream ctor are hidden, and the name transaction clarifies what is being done, or rather, accomplished.




回答4:


<< doesn't send the null byte at the end of "test" into the temporary stringstream("Another ") you created, which is why you see "testher".



来源:https://stackoverflow.com/questions/10110048/c-iostream-corruption-using-stringstream

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!