There are some answers already but I think it is worth add, yet, another answer building on a comment I made. There are essentially three obvious variations how to create a stream which doesn't produce any data:
The simplest version is to just set the stream into a non-operational state. That doesn't match the specification asking for a working stream, though:
std::ostream null(nullptr);
As mentioned by other answers, deriving a stream from std::sttreambuf
and just overriding overflow(int)
to return success but other doing nothing:
struct NullBuffer
: std::streambuf {
int overflow(int c) override { return c; }
};
This is the shortest version to type but as commented already, I don't think this is the fastest approach.
I'd expect a stream buffer actually using a small buffer and also overriding xsputn()
to be faster:
struct NullBuf
: std::streambuf {
char buffer[100];
int overflow(int c) override {
setp(buffer, buffer + sizeof buffer);
return c;
}
std::streamsize xsputn(const char*, std::streamsize n) override {
return n;
}
};
The reason a small buffer is likely better is that the stream doesn't need to check if the buffer is empty and call virtual
function each time. Instead, it would check find that there is buffer and only once in a while call the virtual
function.
When running a benchmark for the three versions I get these results:
Running ./bin/clang--2a/nullstream.tsk
Run on (8 X 24 MHz CPU s)
CPU Caches:
L1 Data 64 KiB
L1 Instruction 128 KiB
L2 Unified 4096 KiB (x8)
Load Average: 1.22, 1.52, 2.04
-----------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------
BM_NullStreambuf 101 ns 101 ns 6883531
BM_NullBuffer 1430 ns 1430 ns 488561
BM_NullBuf 748 ns 748 ns 931309
That is, there is a substantial cost for wanting a non-failing stream rather than simply disabling the stream entirely. Using a buffer and overriding xsput()
is a significant performance boost. Of course, these are micro benchmarks using a particular output although I didn't try to be too smart: I included a string (which should use xsputn()
) and I included an integer with a bigger number of digits to cause the stream to use the std::ostreambuf_iterator<char>
and bypass xsputn()
. The full benchmark is included below.
The code was compiled using
clang++ -std=c++2a -W -Wall -I/usr/local/include -O3 -c -o nullstream.o nullstream.cpp
clang++ -L/usr/local/lib -o nullstream.tsk nullstream.o -lbenchmark -lbenchmark_main
The version of clang
is up to date on an M2 MacBook. I haven't run it on other computers or using a different IOStream implementation but I would expect similar results.
#include <benchmark/benchmark.h>
#include <ostream>
#include <limits>
void run_benchmark(benchmark::State& state, std::ostream& out) {
for (auto _ : state) {
for (int i{0}; i != 10; ++i) {
out << (std::numeric_limits<int>::max() - i)
<< "0123456789012345678901234567890123456789"
<< "\n";
}
}
}
struct NullBuffer
: std::streambuf {
int overflow(int c) override { return c; }
};
struct NullBuf
: std::streambuf {
char buffer[100];
int overflow(int c) override {
setp(buffer, buffer + sizeof buffer);
return c;
}
std::streamsize xsputn(const char*, std::streamsize n) override {
return n;
}
};
static void BM_NullStreambuf(benchmark::State& state) {
std::ostream null(nullptr);
run_benchmark(state, null);
}
static void BM_NullBuffer(benchmark::State& state) {
NullBuffer buf;
std::ostream null(&buf);
run_benchmark(state, null);
}
static void BM_NullBuf(benchmark::State& state) {
NullBuf buf;
std::ostream null(&buf);
run_benchmark(state, null);
}
BENCHMARK(BM_NullStreambuf);
BENCHMARK(BM_NullBuffer);
BENCHMARK(BM_NullBuf);
BENCHMARK_MAIN();