0

我已经编写了 gzstream 1.5 的简单包装器,用于与 rapidjson 0.1(ios,xcode 6.1)一起使用。

问题:我必须在 Peek() 和 Take() 中检查 eof。否则,我将 '\377' (-1) 作为最后一个字符。我知道它是由 std::basic_stream::get() 在 eof 返回的。

有什么更优雅、正确和干净的解决方案?

class GzOutStream {
public:
    GzOutStream(std::string filename) : gs_(new ogzstream(filename.c_str())) {}
    bool Good() { return gs_->good(); }
    void Close() { delete gs_; gs_ = nullptr; }
    size_t Tell() { return gs_->tellp(); }
    void Put(char c) { gs_->put(c); }

    // Not implemented
    char* PutBegin() { return 0; }
    size_t PutEnd(char*) { return 0; }

private:
    ogzstream* gs_;
};

class GzInStream {
public:
    GzInStream(std::string filename) : gs_(new igzstream(filename.c_str())) {}
    bool Good() { return gs_->good(); }
    void Close() { delete gs_; gs_ = nullptr; }
    char Peek() { return gs_->eof()? '\0' : gs_->peek(); }
    char Take() { return gs_->eof()? '\0' : gs_->get(); }
    size_t Tell() { return gs_->tellg(); }
    void Put(char c) { } // Stab

    // Not implemented
    char* PutBegin() { return 0; }
    size_t PutEnd(char*) { return 0; }

private:
    igzstream* gs_;
};
4

1 回答 1

1

下面的答案是为了对手头的问题进行一般性讨论。那时我没有查看 rapidjson 的行为。

您的类旨在成为 gzip 输入流和 rapidjson 之间的粘合逻辑,因此您必须实现 rapidjson 预期的接口。它甚至没有一个很好的功能。rapidjson 预期的接口在 EOF 上返回 '\0',所以这是您必须做的唯一选择。如果您使用的 gzip 流类正在实现 C++ 流模型,您可以使用“示例 istream 包装器”部分中https://github.com/miloyip/rapidjson/blob/master/doc/stream.md中描述的模式",它以通常与 C++ iostream 一起使用的方式进行 EOF 检测。如果您当前的方式适用于 gz 流,您也可以保持原样。


只要您不尝试越过eof,您就基本上遇到了输入流保持良好状态的问题。GzInStream 的接口并没有为用户提供任何在 Peek 或 Take 返回无效值之前检测是否已命中 EOF 的可能性。这是由于 C++ iostream 的设计:大多数情况下,低级 API 不会指示“流结束”,除非您尝试越过它,因此高级 API 不提供此功能,因为它很重要在许多(非文件)情况下实施。

标准 C++ iostreams 的 peek() 和 get() 函数返回int而不是char出于以下原因:它们被指定将从流中读取的字节作为正数返回(在具有 8 位字节的系统上为 0..255),同时返回 eof (-1) 错误。您的 Peek 和 Get 函数无法返回 256 个不同的字节和 EOF 作为不同的返回值,因为 char 无法表示 257 种可能性。就目前而言,您界面的客户必须在之后询问“Good()”从 Peek 或 Take 中获取角色,以确定是否真的有角色要获取。如果您的接口的客户端这样做,那么无论您返回 '\377' 还是 '\0' 或任何其他值都无关紧要,因为无论如何该值都会被忽略。使用“额外”字节的客户端(在我看来)是错误的,除非它旨在忽略您返回的虚假 NUL 字节。

您可以通过不同的方式解决此问题

  • 如上所述修复您的客户,并记录该类的行为。
  • 有 Good() return gs_->good() && !gs_->eof(),依赖于gs_->eof()在过度阅读 eof 之前是真的
  • 从 Peek and Take 中返回一个 int,就像标准 iostream 一样。
  • 从 Peek and Take 中返回 boost::optional,如果遇到 eof 则返回 boost::none
  • 在 EOF 的情况下,从 Peek and Take 中抛出异常。

大多数人会立即拒绝最后提出的修复,因为它违反了“异常不应用于流量控制”的规则。我同意强制客户端使用异常处理来检测 EOF 确实是一种糟糕的风格,但实际上这是唯一不需要更改 Peek 和 Take 的签名也不需要更改其他函数的语义的可能性。我希望第二个建议(改变良好)是你用例的方式。

于 2014-11-22T12:37:56.910 回答