Один из вариантов - создать класс, который содержит ссылку на поток, но удерживает блокировку на протяжении всего времени его существования.Вот простой пример:
#include <iostream>
#include <mutex>
struct LockedOstream {
std::lock_guard<std::mutex> lg_;
std::ostream& os_;
LockedOstream(std::mutex& m, std::ostream& os)
: lg_{m}
, os_{os}
{ }
std::ostream& stream() const { return os_; }
};
int main()
{
std::mutex m;
LockedOstream(m, std::cout).stream() << "foo " << "bar\n";
// ^ locked now ^ unlocked now
}
Это работает до тех пор, пока все операции печати, формирующие одну «единицу» вывода, выполняются в одном выражении.
Редактировать: На самом деле, версия наследования намного лучше, чем я ожидал:
#include <iostream>
#include <mutex>
class LockedOstream : public std::ostream {
static std::mutex& getCoutMutex()
// use a Meyers' singleton for the cout mutex to keep this header-only
{
static std::mutex m;
return m;
}
std::lock_guard<std::mutex> lg_;
public:
// Generic constructor
// You need to pass the right mutex to match the stream you want
LockedOstream(std::mutex& m, std::ostream& os)
: std::ostream(os.rdbuf())
, lg_{m}
{ }
// Cout specific constructor
// Uses a mutex singleton specific for cout
LockedOstream()
: LockedOstream(getCoutMutex(), std::cout)
{ }
};
int main()
{
LockedOstream() << "foo " << "bar\n";
// ^ locked now ^ unlocked now
}
В целом:
(хотя последний иногда спорный, но по крайней мере это хорошая идея, чтобы знать и сделать осознанный выбор).