【问题标题】:Why is std::codecvt only used by file I/O streams?为什么 std::codecvt 仅由文件 I/O 流使用?
【发布时间】:2021-03-06 17:27:01
【问题描述】:

我一直在实现一个编解码器来处理输出流的缩进。它可以像这样使用并且工作正常:

std::cout << indenter::push << "im indentet" << indenter::pop << "\n im not..."

然而,虽然我可以将std::codecvt 注入任何std::ostream,但当我发现我的代码可以与std::coutstd::ofstream 一起工作时,我感到非常困惑,但对于std::ostringstream 甚至都不能而所有这些都继承自基类std::ostream

facet构造正常,代码编译,不抛出异常……只是std::codecvt的成员函数没有被调用。

这对我来说非常令人困惑,我不得不花费大量时间弄清楚 std::codecvt 不会对非文件 I/O 流执行任何操作。

std::codecvt 没有被 std::ostream 继承的所有类使用有什么原因吗?

此外,有没有人知道我可以依靠哪些结构来实现缩进器?

编辑:这是我所指的语言的一部分:

通过 std::basic_fstream 执行的所有文件 I/O 操作都使用流中包含的语言环境的 std::codecvt 方面。

来源:https://en.cppreference.com/w/cpp/locale/codecvt


更新 1:

我做了一个小例子来说明我的问题:

#include <iostream>
#include <locale>
#include <fstream>
#include <sstream>

static auto invocation_counter = 0u;

struct custom_facet : std::codecvt<char, char, std::mbstate_t>
{
  using parent_t = std::codecvt<char, char, std::mbstate_t>;

  custom_facet() : parent_t(std::size_t { 0u }) {}

  using parent_t::intern_type;
  using parent_t::extern_type;
  using parent_t::state_type;

  virtual std::codecvt_base::result do_out (state_type& state, const intern_type* from, const intern_type* from_end, const intern_type*& from_next,
                                                               extern_type* to, extern_type* to_end, extern_type*& to_next) const override
  {
    while (from < from_end && to < to_end)
    {
      *to = *from;

      to++;
      from++;
    }

    invocation_counter++;

    from_next = from;
    to_next = to;

    return std::codecvt_base::noconv;
  }

  virtual bool do_always_noconv() const throw() override
  {
    return false;
  }
};

std::ostream& imbueFacet (std::ostream& ostream)
{
  ostream.imbue(std::locale { ostream.getloc(), new custom_facet{} });

  return ostream;
}

int main()
{
  std::ios::sync_with_stdio(false);

  std::cout << "invocation_counter = " << invocation_counter << "\n";

  {
    auto ofstream = std::ofstream { "testFile.txt" };

    ofstream << imbueFacet << "test\n";
  }

  std::cout << "invocation_counter = " << invocation_counter << "\n";

  {
     auto osstream = std::ostringstream {};

     osstream << imbueFacet << "test\n";
  }

  std::cout << "invocation_counter = " << invocation_counter << "\n";
}

除了invocation_counter 之外,我会在std::ostringstream 流式传输后增加,但事实并非如此。


更新 2:

经过更多研究,我发现我可以使用std::wbuffer_converter。引用https://en.cppreference.com/w/cpp/locale/wbuffer_convert

std::wbuffer_convert 是流缓冲区类型的包装器 std::basic_streambuf&lt;char&gt; 这使它看起来像 std::basic_streambuf&lt;Elem&gt;。所有 I/O 通过 std::wbuffer_convert 经历由定义的字符转换 方面编解码器。 [...]

这个类模板进行隐式字符转换 std::basic_filebuf 的功能可用于任何 std::basic_streambuf.

这样我可以将构面应用于std::ostringstream

auto osstream = std::ostringstream {};

osstream << "test\n";
  
auto facet = custom_facet{};
  
std::wstring_convert<custom_facet, char> conv;
  
auto str = conv.to_bytes(osstream.str());

但是,我无法使用流式运算符 &lt;&lt; 连接构面。

这让我更加困惑,为什么 std::codecvt 不是所有输出流都隐式使用的。所有输出流都继承自std::basic_streambuf,其接口适合使用std::codecvt,只是使用输入和输出字符序列,完全在std::basic_streambuf中实现。

那么为什么std::codecvt的解析是在std::basic_filebuf而不是std::basic_streambuf中实现的呢?毕竟std::basic_filebuf 继承了std::basic_streambuf...

要么我对流在 C++ 中的工作方式有一些基本的误解,要么std::codecvt 在标准中的集成很差。也许这就是它被标记为已弃用的原因?

【问题讨论】:

  • 我不知道方面的恶作剧,但也许只使用std::format 而完全忘记 iostreams?
  • @PasserBy 我曾考虑过使用std::format,但使用流的优势在于,它可以使用它获得的任何流。我在我的 json 序列化程序中使用缩进器,它能够使用从std::ostream 派生的对象的引用写入任何输出流。这样我就可以序列化为std::ofstreamstd::ostringstreamstd::cout。使用std::format 我会失去这种灵活性,因为序列化程序会递归调用每个对象成员进行序列化。
  • C++98 的 std::codecvt 没有被弃用,只有从它派生的 C++11 的 Unicode 转换语言环境被弃用。

标签: c++ file facet ostream codecvt


【解决方案1】:

std::codecvt facet 最初旨在处理 diskmemory 字符表示之间的 I/O 转换。引自 Bjarne Stroustrup 的The C++ Programming Language 第四版的39.4.6 段:

有时,存储在文件中的字符表示与主内存中相同字符的期望表示不同。 ... codecvt 方面提供了一种机制,用于在读取或写入字符时将字符从一种表示形式转换为另一种表示形式。

预期的目的是使用std::codecvt 仅用于在文件(磁盘)和内存之间调整字符,这部分回答了您的问题:

为什么 std::codecvt 只用于文件 I/O 流?

docs我们看到:

通过std::basic_fstream&lt;CharT&gt; 执行的所有文件I/O 操作都使用流中包含的语言环境的std::codecvt&lt;CharT, char, std::mbstate_t&gt; 方面。

然后回答为什么std::ofstream(使用基于文件的流缓冲区)和std::coutlinked to standard output FILE stream)调用std::codecvt的问题。

现在,要使用高级std::ostream 接口,您需要提供底层streambufstd::ofstream 提供filebufstd::ostringstream 提供stringbuf(与std::codecvt 的使用无关)。请参阅streams 上的这篇文章,其中还强调了以下内容:

...在ofstream的情况下,还有一些额外的函数转发给filebuf接口中的附加函数

但是,当您有 std::ostringstream 时调用 std::codecvt 的字符转换功能,这是一个带有底层 std::ostreamstd::basic_streambuf,您可以使用 std::wbuffer_convert,如您的帖子中所示。

您在第二次更新中只使用了std::wstring_convert,而不是std::wbuffer_convert

使用std::wbuffer_convert 时,您可以将原始std::ostringstream 包装为std::ostream,如下所示:

// Create a std::ostringstream
auto osstream = std::ostringstream{};

// Create the wrapper for the ostringstream
std::wbuffer_convert<custom_facet, char> wrapper(osstream.rdbuf());

// Now create a std::ostream which uses the wrapper to send data to
// the original std::ostringstream
std::ostream normal_ostream(&wrapper);
normal_ostream << "test\n";

// Flush the stream to invoke the conversion
normal_ostream << std::flush;

// Check the invocation_counter
std::cout << "invocation_counter after wrapping std::ostringstream with "
                "std::wbuffer_convert = "
            << invocation_counter << "\n";

连同完整的示例here,输出将是:

invocation_counter start of test1 = 0
invocation_counter after std::ofstream = 1
> test printed to std::cout
invocation_counter after std::cout = 2
invocation_counter after std::ostringstream (should not have changed)= 2
ic after test1 = 2
invocation_counter after std::ostringstream with std::wstring_convert = 3
ic after test2 = 3
invocation_counter after wrapping std::ostringstream with std::wbuffer_convert = 4
ic after test3 = 4

结论

std::codecvt 用于在磁盘和内存表示之间进行转换。这就是为什么std::codecvt 实现只在使用底层filebuf 的流中调用,例如std::ofstreamstd::cout。 但是,可以使用 std::wbuffer_convert 将使用底层 stringbuf 的流包装到 std::ostream 实例中,然后该实例将调用底层 std::codecvt

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-17
    • 2023-03-08
    • 2010-11-12
    • 1970-01-01
    • 2015-07-05
    • 1970-01-01
    • 2019-11-17
    相关资源
    最近更新 更多