【问题标题】:How to translate C file pointers into generic C++ streams?如何将 C 文件指针转换为通用 C++ 流?
【发布时间】:2021-01-28 17:08:47
【问题描述】:

考虑以下 C 代码:

void openFile(const char *mode, char *filename, FILE **fileptr)
{
  ...
  *fileptr = fopen(filename, mode);
  ...
}

FILE *logstream;
if (LOG_FILE_ENABLED)
{
  openFile("w", "mylogfile.txt", logstream);
}
else
{
  logstream = stderr;
}

fprintf(logstream, "[DEBUG] Some debug message...\n");
fclose(logstream);

我正在尝试将其翻译成惯用的 C++。我怎样才能重载openFile() 使其需要std::ofstream,但保持logstream 流不可知?我假设它会是这样的:

void openFile(const char *mode, char *filename, std::ofstream &ofs)
{
  ...
  ofs.open(filename);
  ...
}

std::ostream logstream;
if (LOG_FILE_ENABLED)
{
  logstream = std::ofstream();
  openFile("w", "mylogfile.txt", logstream);
}
else
{
  logstream = std::cerr;
}

logstream << "[DEBUG] Some debug message..." << std::endl;
logstream.close();

然而这显然是非常不正确的——你甚至不能像这样初始化一个普通的std::ostream。我应该如何处理 - 最好同时避免使用原始指针?

【问题讨论】:

  • 您没有显示FILE *logstream 是如何关闭的(如果有的话)以及如何处理的。
  • 失败原因:A) 无法复制流。想一想同一流的两个副本同时写入同一底层介质可能带来的乐趣。 B)Object Slicing。解决方法:没有。需要查看更多背景信息,看看我脑海中浮现的任何想法是否真的适用。
  • void openFile(const char *mode, char *filename, FILE *fileptr) 不能很好地工作。我建议您改为返回 FILE 指针。
  • 糟糕,这是一个双指针。你明白了。它将以fclose(logsream) 关闭
  • IOStreams 因其糟糕的设计和可用性而臭名昭著。使用printf 样式格式字符串或std::format,忘记IOStreams 在现代代码中的格式,IMO。

标签: c++ iostream


【解决方案1】:

我会将实际工作转移到一个单独的函数或 lambda,它以 std::ostream 作为输入。然后调用者可以决定传入哪种类型的std::ostream,例如:

void doRealWork(std::ostream &log)
{
    ...
    log << "[DEBUG] Some debug message..." << std::endl;
    ...
}

if (LOG_FILE_ENABLED)
{
  std::ofstream log("mylogfile.txt");
  doRealWork(log);
}
else
{
  doRealWork(std::cerr);
}

或者:

auto theRealWork = [&](std::ostream &&log)
{
    ...
    log << "[DEBUG] Some debug message..." << std::endl;
    ...
}

if (LOG_FILE_ENABLED) {
  theRealWork(std::ofstream{"mylogfile.txt"});
} else {
  theRealWork(static_cast<std::ostream&&>(std::cerr));
}

更新:否则,您可以做更多类似的事情:

using unique_ostream_ptr = std::unique_ptr<std::ostream, void(*)(std::ostream*)>;

unique_ostream_ptr logstream;
if (LOG_FILE_ENABLED) {
  logstream = unique_ostream_ptr(new std::ofstream("mylogfile.txt"), [](std::ostream *strm){ delete strm; });
} else {
  logstream = unique_ostream_ptr(&std::cerr, [](std::ostream *){});
}

*logstream << "[DEBUG] Some debug message...\n";

或者:

using shared_ostream_ptr = std::shared_ptr<std::ostream>;

shared_ostream_ptr logstream;
if (LOG_FILE_ENABLED) {
  logstream = std::make_shared<std::ofstream>("mylogfile.txt");
} else {
  logstream = shared_ostream_ptr(&std::cerr, [](std::ostream*){});
}

*logstream << "[DEBUG] Some debug message...\n";

【讨论】:

  • 这绝对不会扩展 - 有数千个 fprintf() 调用,我不能将它们全部放在 if 语句中。此外,在设置LOG_FILE_ENABLED 之前,日志变量是在外部定义的。我需要一种方法,无论是记录到 stderr 还是文件,记录调用都是相同的。
  • 这里的动态分配太过分了。
  • @AsteroidsWithWings 也许吧,但鉴于 OP 的设计限制,我认为没有其他办法。
【解决方案2】:

C++ 流库的设计非常古老。 尽管如此 - 它的基本思想是 ostream 或 istream 只是流缓冲区上的包装器对象。

所以你可以试试下面的代码:

std::ostream get_log(bool str) {
    if (str) return std::ostream(new std::stringbuf());
     // else
    std::filebuf* f = new std::filebuf();
    f->open("log", std::ios_base::out);
    return std::ostream(f);
}

但是,正如我所提到的,这是非常古老的设计 - 所以没有 RAII - 这个缓冲区不属于流 - 你需要自己删除它:

int main() {
    std::ostream log = get_log(true);
    log << "aaa";
    std::cout << static_cast<std::stringbuf&>(*log.rdbuf()).str();
    
    delete log.rdbuf(); // (!)
}

所以这个用处不大。

所以我的最后建议 - 在 ostream 上使用智能指针 - 像这样:

std::unique_ptr<std::ostream> get_log(bool str) {
    if (str) return new std::ostringstream();
    std::ofstream* f = new std::ofstream();
    f->open("log", std::ios_base::out);
    return f;
}

int main() {
    auto log = get_log(true);
    *log << "aaa";
}

【讨论】:

  • 您可以将std::ofstream* f = new std::ofstream(); f-&gt;open("log", std::ios_base::out); return f; 简化为return new std::ofstream("log");
  • 这似乎是要走的路。非常感谢!!
【解决方案3】:

你快到了;您只需要注意范围规则,以及除了抽象基类之外没有 std::ostream 这样的东西。

所以:

std::ostream* logstreamPtr = nullptr;
std::ofstream ofs;
if (LOG_FILE_ENABLED)
{
  logstreamPtr = &ofs;
  openFile("w", "mylogfile.txt", ofs);
}
else
{
  logstreamPtr = &std::cerr;
}

std::ostream& logstream = *logstreamPtr;

logstream << "[DEBUG] Some debug message..." << std::endl;
logstream.close();

您不需要引用logstream,但它使您不必在以后重复引用logstreamPtr,这会很无聊。

不要害怕这个原始指针。这是指针最纯粹的应用。如果你愿意,你可以沿着智能指针路线走,但你一无所获并且失去了可读性(在某些情况下,还失去了性能)。

顺便说一句,如果您担心性能,不要为每条消息打开和关闭日志文件;太浪费了。

【讨论】:

  • logstream.close() 不起作用:std::ostream has no member 'close'。不过,其他一切似乎都有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-10
  • 1970-01-01
  • 2012-04-27
  • 1970-01-01
  • 1970-01-01
  • 2011-10-27
  • 2016-08-24
相关资源
最近更新 更多