如何将 C 文件指针转换为通用 C++ 流?

How to translate C file pointers into generic C++ streams?

考虑以下 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。我应该如何处理这个问题 - 最好避免使用原始指针?

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";
}

我会将实际工作转移到一个单独的函数或 lambda 中,该函数或 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";

你快到了;您只需要注意范围规则,以及除了抽象基础 class.

之外没有 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,那会很无聊。

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

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