如何在 C++ 中编写 ofstream 向量,它接收所有不同的输出流,如 cout、字符串流和 ofstream
How to write vector of ostreams in C++ which takes in all the different output streams like cout, ostringstream and ofstream
我正在尝试实现一个记录器,它可以注册到多个流,如 ostringstream、ofstream 等。我试图实现这样的功能
void register_stream(std::ostream& a);
向量如下
std::vector<std::ostream> streams;
寄存器流和运算符重载如下
void logger::register_stream(std::ostream &a)`
{
streams.push_back(a);
}
template <typename T>
void logger::operator<<(T const& value)
{
for (auto stream : streams)
{
(stream) << value;
}
}
我正在尝试实现一个记录器,以在单个运算符“<<
”调用上写入所有已注册的流。
调用代码如下:
std::ostringstream os;
std::ofstream f;
logger l;
l.register_stream(f);
l.register_stream(os);
l << "log this";
我收到一个错误:
C2280: std::basic_ostream<char,std::char_traits<char>>::basic_ostream(const std::basic_ostream<char,std::char_traits<char>> &)
: attempting to reference a deleted function
如有任何帮助,我们将不胜感激。
您不能存储流的副本,它们不可复制。您必须改为存储 std::reference_wrapper
或指针。
class logger {
std::vector<std::reference_wrapper<std::ostream>> streams;
public:
void register_stream(std::ostream &stream) {
streams.emplace_back(stream);
}
template <typename T>
void operator<<(T const &value) {
for (auto &stream : streams) { // note the '&'
stream.get() << value;
}
}
};
或者如果您希望能够像 l << "log" << " this";
:
那样链接您的电话
template <typename T>
logger & operator<<(T const &value) {
for (auto &stream : streams) {
stream.get() << value;
}
return *this;
}
ostream
对底层 streambuf
进行格式化和写入。因此,当您多次使用 operator<<
时,它会不必要地多次格式化相同的输入。更优化的方法是格式化一次,然后使用未格式化输出函数 ostream::write
将格式化输出复制到多个底层 stream
s。
拥有 std::ostream
接口很方便,这样您就可以将它传递给需要 std::ostream
接口的现有函数。
您基本上需要自定义 streambuf
实现。从头开始写一个是很好的学习经验,但乏味且容易出错,因为 streambuf
interface is somewhat hard to comprehend and implement correctly. Use The Boost Iostreams Library 相反。
工作示例:
#include <boost/iostreams/stream.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
struct MultiSink {
using char_type = char;
struct category
: boost::iostreams::sink_tag
, boost::iostreams::flushable_tag
{};
std::vector<std::ostream*> sinks_;
std::streamsize write(char const* s, std::streamsize n) {
for(auto sink : sinks_)
sink->write(s, n);
return n;
}
bool flush() {
for(auto sink : sinks_)
sink->flush();
return true;
}
void add_sink(std::ostream& s) {
sinks_.push_back(&s);
}
void remove_sink(std::ostream& s) {
sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), &s), sinks_.end());
}
};
int main() {
MultiSink sink;
boost::iostreams::stream<MultiSink> stream(sink);
stream->add_sink(std::cout);
stream->add_sink(std::cerr);
stream << "Hello, world!" << std::endl;
}
请注意,代码假定已注册流的寿命超过多接收器。如果不是这种情况,您需要在销毁之前从多接收器注销流。
您有几个概念性问题需要解决:
std::cout
是一个全局对象,但是std::ostringstream
和std::ofstream
是类型。将它们讨论为可互换的输出是一个类别错误
std::cout
是具有程序生命周期的全局对象,但您创建的任何 std::ofstream
实例 可能具有不同的生命周期。你需要一些方法来判断它的生命周期是否可以在你的记录器 之前 结束(这不是 cout
的问题,除非你的记录器也有程序生命周期),或者让记录器知道 它 负责您的流的生命周期。
- 拥有
std::vector<std::ostream> streams
是行不通的,因为:
- 它按值复制流,这是明确禁止的(参见删除的复制构造函数here)
- 就算允许了也会因为object slicing.
而被破坏
有了这些,Maxim 的答案很好,但没有解决流的生命周期问题——如果这些都不是问题(你很乐意静态地保证每个注册的流都比记录器长寿),那么它就是一个很好的解决方案。
如果您确实需要一些额外的支持来管理对象的生命周期,您需要更详细的东西 - 例如。存储知道记录器是否拥有给定流的代理对象。
"If you do need some extra support to manage object lifetimes, you require something a bit more elaborate - eg. storing proxy objects that know whether or not the logger owns a given stream." – 没用
我当前项目中这个问题的当前解决方案看起来很像:
using ostream_deleter = std::function<void(std::ostream *)>;
using ostream_ptr = std::unique_ptr<std::ostream, ostream_deleter>;
这允许您存储一个新的流对象,具有所有权,例如
ostream_deleter d{std::default_delete<std::ostream>{}};
ostream_ptr fileStream{new std::ofstream{"/tmp/example.foo"}, std::move(d)};
请注意,出于异常安全的原因,您应该首先创建类型擦除的删除器。
它还允许您使用已知比您的记录器寿命更长的全局流:
ostream_ptr coutStream{&std::cout, [](std::ostream &) {}};
如果您想要更多自文档语法,还有一个 null_deleter
in Boost。
不幸的是,可能在内部 重定向 的流仍然存在问题。实际上,C++ 支持将任何流的输出(或输入)重定向到不同的流缓冲区,例如
std::ofstream logStream{"/tmp/my.log"};
const auto oldBuf = std::cout.rdbuf(logStream.rdbuf());
std::cout << "Hello World.\n"; // output redirected to /tmp/my.log
std::cout.rdbuf(oldBuf); // undo redirection
这里的问题是流缓冲区的生命周期与 logStream
相关,而不是 std::cout
。这使任何生命周期管理 rsp 变得复杂。提出一个完全通用的解决方案,几乎是不可能的。当然,纪律和惯例应该仍然有很大帮助,而且不可否认,流重定向是一个相当晦涩且很少使用的功能。
我正在尝试实现一个记录器,它可以注册到多个流,如 ostringstream、ofstream 等。我试图实现这样的功能
void register_stream(std::ostream& a);
向量如下
std::vector<std::ostream> streams;
寄存器流和运算符重载如下
void logger::register_stream(std::ostream &a)`
{
streams.push_back(a);
}
template <typename T>
void logger::operator<<(T const& value)
{
for (auto stream : streams)
{
(stream) << value;
}
}
我正在尝试实现一个记录器,以在单个运算符“<<
”调用上写入所有已注册的流。
调用代码如下:
std::ostringstream os;
std::ofstream f;
logger l;
l.register_stream(f);
l.register_stream(os);
l << "log this";
我收到一个错误:
C2280:
std::basic_ostream<char,std::char_traits<char>>::basic_ostream(const std::basic_ostream<char,std::char_traits<char>> &)
: attempting to reference a deleted function
如有任何帮助,我们将不胜感激。
您不能存储流的副本,它们不可复制。您必须改为存储 std::reference_wrapper
或指针。
class logger {
std::vector<std::reference_wrapper<std::ostream>> streams;
public:
void register_stream(std::ostream &stream) {
streams.emplace_back(stream);
}
template <typename T>
void operator<<(T const &value) {
for (auto &stream : streams) { // note the '&'
stream.get() << value;
}
}
};
或者如果您希望能够像 l << "log" << " this";
:
template <typename T>
logger & operator<<(T const &value) {
for (auto &stream : streams) {
stream.get() << value;
}
return *this;
}
ostream
对底层 streambuf
进行格式化和写入。因此,当您多次使用 operator<<
时,它会不必要地多次格式化相同的输入。更优化的方法是格式化一次,然后使用未格式化输出函数 ostream::write
将格式化输出复制到多个底层 stream
s。
拥有 std::ostream
接口很方便,这样您就可以将它传递给需要 std::ostream
接口的现有函数。
您基本上需要自定义 streambuf
实现。从头开始写一个是很好的学习经验,但乏味且容易出错,因为 streambuf
interface is somewhat hard to comprehend and implement correctly. Use The Boost Iostreams Library 相反。
工作示例:
#include <boost/iostreams/stream.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
struct MultiSink {
using char_type = char;
struct category
: boost::iostreams::sink_tag
, boost::iostreams::flushable_tag
{};
std::vector<std::ostream*> sinks_;
std::streamsize write(char const* s, std::streamsize n) {
for(auto sink : sinks_)
sink->write(s, n);
return n;
}
bool flush() {
for(auto sink : sinks_)
sink->flush();
return true;
}
void add_sink(std::ostream& s) {
sinks_.push_back(&s);
}
void remove_sink(std::ostream& s) {
sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), &s), sinks_.end());
}
};
int main() {
MultiSink sink;
boost::iostreams::stream<MultiSink> stream(sink);
stream->add_sink(std::cout);
stream->add_sink(std::cerr);
stream << "Hello, world!" << std::endl;
}
请注意,代码假定已注册流的寿命超过多接收器。如果不是这种情况,您需要在销毁之前从多接收器注销流。
您有几个概念性问题需要解决:
std::cout
是一个全局对象,但是std::ostringstream
和std::ofstream
是类型。将它们讨论为可互换的输出是一个类别错误std::cout
是具有程序生命周期的全局对象,但您创建的任何std::ofstream
实例 可能具有不同的生命周期。你需要一些方法来判断它的生命周期是否可以在你的记录器 之前 结束(这不是cout
的问题,除非你的记录器也有程序生命周期),或者让记录器知道 它 负责您的流的生命周期。- 拥有
std::vector<std::ostream> streams
是行不通的,因为:- 它按值复制流,这是明确禁止的(参见删除的复制构造函数here)
- 就算允许了也会因为object slicing. 而被破坏
有了这些,Maxim 的答案很好,但没有解决流的生命周期问题——如果这些都不是问题(你很乐意静态地保证每个注册的流都比记录器长寿),那么它就是一个很好的解决方案。
如果您确实需要一些额外的支持来管理对象的生命周期,您需要更详细的东西 - 例如。存储知道记录器是否拥有给定流的代理对象。
"If you do need some extra support to manage object lifetimes, you require something a bit more elaborate - eg. storing proxy objects that know whether or not the logger owns a given stream." – 没用
我当前项目中这个问题的当前解决方案看起来很像:
using ostream_deleter = std::function<void(std::ostream *)>;
using ostream_ptr = std::unique_ptr<std::ostream, ostream_deleter>;
这允许您存储一个新的流对象,具有所有权,例如
ostream_deleter d{std::default_delete<std::ostream>{}};
ostream_ptr fileStream{new std::ofstream{"/tmp/example.foo"}, std::move(d)};
请注意,出于异常安全的原因,您应该首先创建类型擦除的删除器。
它还允许您使用已知比您的记录器寿命更长的全局流:
ostream_ptr coutStream{&std::cout, [](std::ostream &) {}};
如果您想要更多自文档语法,还有一个 null_deleter
in Boost。
不幸的是,可能在内部 重定向 的流仍然存在问题。实际上,C++ 支持将任何流的输出(或输入)重定向到不同的流缓冲区,例如
std::ofstream logStream{"/tmp/my.log"};
const auto oldBuf = std::cout.rdbuf(logStream.rdbuf());
std::cout << "Hello World.\n"; // output redirected to /tmp/my.log
std::cout.rdbuf(oldBuf); // undo redirection
这里的问题是流缓冲区的生命周期与 logStream
相关,而不是 std::cout
。这使任何生命周期管理 rsp 变得复杂。提出一个完全通用的解决方案,几乎是不可能的。当然,纪律和惯例应该仍然有很大帮助,而且不可否认,流重定向是一个相当晦涩且很少使用的功能。