有没有办法创建一个基本上不执行任何操作的 ostream 实例?
例如:
std::ostream dummyStream(...);
dummyStream << "Nothing will be printed";
我可以只创建一个 ostringstream,但数据将被缓冲(我真的不想用它们做任何事情,所以它增加了无用的开销)。
有什么想法吗?
[编辑]发现这个相关问题适合我的需要。然而,我认为有一个答案可能会很有用,说明如何使用标准 c++ 创建一个valid(无坏位)输出流。
您需要一个自定义的streambuf。
class NullBuffer : public std::streambuf
{
public:
int overflow(int c) { return c; }
};
然后您可以在任何 ostream 类中使用此缓冲区
NullBuffer null_buffer;
std::ostream null_stream(&null_buffer);
null_stream << "Nothing will be printed";
streambuf::overflow
是当缓冲区必须将数据输出到流的实际目的地时调用的函数。上面的 NullBuffer
类在调用溢出时不执行任何操作,因此使用它的任何流都不会产生任何输出。
如果这是为了禁用日志输出,您的
dummyStream
仍然会导致参数被评估。如果您想在禁用日志记录时最大程度地减少影响,可以依赖条件,例如:
#define debugStream \
if (debug_disabled) {} \
else std::cerr
所以如果你有这样的代码:
debugStream << "debugging output: " << foo() << std::endl;
如果
debug_disabled
为 true,则不会评估任何参数。
已经有一些答案,但我认为值得添加,但是,另一个答案是基于我所做的评论。 如何创建不产生任何数据的流,本质上存在三种明显的变化:
最简单的版本是将流设置为非操作状态。但这与要求工作流的规范不符:
std::ostream null(nullptr);
正如其他答案所提到的,从
std::sttreambuf
派生流并覆盖 overflow(int)
以返回成功,但其他什么都不做:
struct NullBuffer
: std::streambuf {
int overflow(int c) override { return c; }
};
这是输入的最短版本,但正如已经评论的那样,我认为这不是最快的方法。
我希望流缓冲区实际上使用一个小缓冲区并且覆盖
xsputn()
更快:
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;
}
};
小缓冲区可能更好的原因是流不需要每次都检查缓冲区是否为空并调用
virtual
函数。相反,它会检查是否有缓冲区,并且偶尔调用 virtual
函数。
运行三个版本的基准测试时,我得到以下结果:
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
也就是说,想要一个无故障的流而不是简单地完全禁用该流需要付出巨大的代价。使用缓冲区并覆盖
xsput()
可以显着提高性能。当然,这些是使用特定输出的微观基准,尽管我并没有试图太聪明:我包含了一个字符串(应该使用xsputn()
),并且包含了一个具有更多位数的整数以使流使用 std::ostreambuf_iterator<char>
并绕过 xsputn()
。完整的基准测试如下。
代码是使用
编译的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
M2 MacBook 上的
clang
版本是最新的。我还没有在其他计算机上运行它或使用不同的 IOStream 实现,但我希望得到类似的结果。
#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();
新建流类的基本方法是:
std::streambuf
派生一个类;std::ostream
派生一个类,其中包含一个成员,即您的 Streambuf 类。不过,恐怕您无法摆脱格式化步骤。
希望这能给您一些指导;抱歉,我没有时间将其扩展为完整答案。
更新:有关详细信息,请参阅john的回答。
对于运行时可控的日志消息重定向,结合了 john 和 Sjoerd 思想的独立解决方案:
class DebugStream {
private:
class NullStream : public std::ostream {
private:
class NullBuffer : public std::streambuf {
public:
int overflow(int c) override { return c; }
} buffer_;
public:
NullStream() : std::ostream(&buffer_) {}
} null_;
std::ostream &output_;
bool enabled_;
public:
DebugStream(std::ostream &output = std::cout) : output_(output), enabled_(false) {}
void enable(const bool enable) { enabled_ = enable; }
template <typename T> std::ostream& operator<<(const T &arg) {
if (enabled_) return output_ << arg;
else return null_ << arg;
}
};
extern DebugStream debug_stream;
#define TRACE_ENABLE(x) debug_stream.enable(x)
#define TRACELN(x) debug_stream << x << std::endl
#define TRACE(x) debug_stream << x
然后你可以做这样的事情:
TRACELN("The value of x is " << x " and the value of y is " << y);
使用
#define
将跟踪宏清空语句,从发布版本中完全删除跟踪语句也很容易。
不过,您仍然需要在全局某个地方定义
debug_stream
。
如果您担心调试器的开销,那么您可以编写一个非常简单的代码来消除编译时的调试消息。这就是我在 C++ 程序中使用的。
#include <iostream>
#define DEBUGGING // Define this in your config.h or not.
#ifdef DEBUGGING
/*
* replace std::cout with your stream , you don't need to
* worry about the context since macros are simply search
* and replace on compilation.
*/
#define LOG_START std::cout <<
#define LOG_REDIR <<
#define LOG_END << std::endl;
#else
#define LOG_START if(0){(void)
#define LOG_REDIR ;(void)
#define LOG_END ;}
#endif // DEBUGGING
int main(){
LOG_START "This is a log message " LOG_REDIR "Still a log message." LOG_END;
return 0;
}
现在在创建项目时,检查用户是否想要禁用日志记录,如果是,只需取消定义 DEBUGGING 宏或您选择检查的任何宏。
现在你的代码将被编译器优化,因为当任何东西被作废时,它不会被包含在生成的二进制文件中(大多数时候),从而为二进制生产做好准备。
这是一个基于 john 的答案和 Sjoerd 的评论的现代 C++ 解决方案:
#include <ostream>
#include <streambuf>
// Adapted from https://stackoverflow.com/a/11826666
class oblivion_stream final : public std::ostream {
public:
oblivion_stream() noexcept : std::ostream(&os_buffer_)
{
}
private:
class oblivion_stream_buffer_ final : public std::streambuf {
protected:
[[nodiscard]] auto overflow(int_type ch) noexcept -> int_type override
{
return ch;
}
};
oblivion_stream_buffer_ os_buffer_{};
};
我需要一个 ostream 类型的空流,所以我做了这样的事情:
struct NullStream: public stringstream {
NullStream(): stringstream() {}
};
template<typename T>
void operator<<(const NullStream&, const T&) {}
申请代码:
NullStream ns;
ostream &os = ns;
os << "foo";
真正的问题是我继承但不关心的所有公共方法,所以我只是懒得重写它们。