As has been pointed out in the comments, C++98 doesn't offer any notion of threads in the standard, so you can't answer this question by reference to the standard.
However any reasonable C++98 implementation intended to be used with threads (i.e., most of them outside of some embedded markets) would almost certainly offer at least a more-or-less safe std::cout
for "plain" use cases. After all, using that stream for output is very common, and using it across threads in a threaded program will also be very common.
Unfortunately, that's about as much as you can say with specificity. Note, in particular, that I'm not even defining "safe" in a particular way. At least you probably expect that it won't crash, corrupt your program or produce output "out of thin air". Beyond that, it depends.
What Does it Depend On
The next step is to check the implementation itself. Hopefully you have the source1!
Typically you'll find that the implementation has some thread-agnostic code (e.g,. copying into stack-local buffers) and at some point locks and then manipulates shared state inside the std::cout
object. When and where it locks is important.
Single String
For example, one would hope that the output of a single item on two threads, like std::cout << "AAAA"
on thread 1 and std::cout << "BBBB"
on thread 2 would at least result in "not-interleaved" output of AAAABBBB
or BBBBAAAA
but never BBAAAABB
or something like that. This may not be true for implementations that buffer in certain ways! For example, the implementation may lock, then copy as much into an internal buffer as possible and if full, output, and then unlock and proceed again.
Keep in mind that even the std::cout
object is totally locked (i.e., essentially the entire operator<<(const char *)
is run under a lock), you might see interleaving of even single string output when it is run concurrently with other code that is writing to stdout
but through a mechanism other than std::cout
- such as printf()
.
In this case the C/C++ runtimes generally won't share any lock, and you'll be relying on the locking of the underlying write()
-type call offered by the OS. Although this call is fully locked and thread-safe, it might not write the entire string in one shot (this is common, for example, when an intermediate buffer fills up such as when writing to a pipe).
Multiple Operators
The main problem with the stream interface is multiple operators. Something like std::cout << "AAA" << "BBB" ...
is almost always turned into multiple calls on the shared std::cout
object. Without external locking on your part, these are free to be interleaved in any way with other threads. Critically, this pretty much means the stream manipulators in iomanip
are impossible to use safely. Something like std::cout << std::hex << 123 << std::dec
will modify the stream globally, and some other thread that doesn't want hex
output might run at the wrong time and get it anyways. Furthermore, the fact that the stream formatting state and flags can change in the middle of an operation may produce some weird, wonderful or downright terrible results.
1 Certainly the source is available for open source runtimes like libstc++ (used by gcc) and libc++ (used by default by LLVM on most non-Linux platforms). Microsoft appears to be providing the source for their C runtime as well.
std::cout
thread safe in C++98? no. it wasn't written at the time to be thread safe. C++98 knows nothing about threads. it's not "thread safe", it's simply non existent for the standard presective. – David Haim