Я бы рассмотрел только один operator<<
и только в этой функции-члене, блокирующей мьютекс.Так что удерживайте блокировку только тогда, когда вы собираетесь писать.
И вместо статической переменной (которая в основном совпадает с глобальной переменной, поэтому вы не можете иметь несколько регистраторов) для хранения std::ostringstream
, иметь переменную-член, содержащую std::ostream&
.Это будет означать, что при записи нескольких вещей через несколько BasicLogger
они будут казаться смешанными, но это уже было проблемой, когда несколько потоков пишут через один и тот же BasicLogger
.
Чтобы устранить проблему, которая выглядит следующим образом:
BasicLogger l;
// Thread 1:
l << 1 << 2;
// Thread 2:
l << 3 << 4;
// Output is one of:
1234
1324
1342
3124
3142
3412
// Ideally it should only be
1234
3412
// (Pretend `1` is something like "x is: " and `3` is "y is: ")
// (You wouldn't want "x is: {y}" or "x is: y is: {x} {y}")
У вас может быть одна функция, которая записывает много вещей, а затем блокирует их, принимая аргументы с переменным числом аргументов.(Записано как BasicLogger::write
в моем примере)
Это будет выглядеть так:
#include <iostream>
#include <utility>
#include <mutex>
#include <thread>
class BasicLogger {
public:
enum SEVERITY {
CRITICAL,
ERROR,
WARNING
};
// Consider logging to std::cerr by default instead
explicit BasicLogger(SEVERITY s = BasicLogger::ERROR, std::ostream& out = std::cout)
: severity(s), output(&out) {}
explicit BasicLogger(std::ostream& out = std::cout)
: severity(BasicLogger::ERROR), output(&out) {}
BasicLogger(const BasicLogger&) = default;
template<typename T>
BasicLogger& operator<<(T&& obj) {
std::lock_guard<std::mutex> lock(stream_mutex);
(*output) << std::forward<T>(obj);
return *this;
}
template<typename... T>
void write(T&&... obj) {
std::lock_guard<std::mutex> lock(stream_mutex);
((*output) << ... << std::forward<T>(obj));
}
std::ostream& get_output() noexcept {
return *output;
}
const std::ostream& get_output() const noexcept {
return *output;
}
BasicLogger& operator=(const BasicLogger&) = default;
SEVERITY severity;
private:
std::ostream* output;
static std::mutex stream_mutex;
};
std::mutex BasicLogger::stream_mutex;
int main() {
BasicLogger l(std::cerr);
int x = 0, y = 1;
std::thread t1([&]() {
l.write("x is: ", x, '\n');
});
std::thread t2([&]() {
l.write("y is: ", y, '\n');
});
t1.join();
t2.join();
}
Или у вас может быть даже operator<<(std::tuple<T...>)
, а вместо l.write(...)
, l << std::tie(...)
.
Но обратите внимание на различия между этим и вашим классом.Ваш класс будет писать только один раз, используя пробел, чтобы иметь временную ostringstream
, тогда как это записывает непосредственно в нужную ostream
несколько раз.