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

use*_*616 5 c++ file facet ostream codecvt

我一直在实现一个用于处理输出流标识的 codecvt。它可以像这样使用并且工作正常:

std::cout << indenter::push << "im indentet" << indenter::pop << "\n im not..."
Run Code Online (Sandbox Code Playgroud)

然而,虽然我可以将 an 灌输std::codecvt给任何std::ostream我,但当我发现我的代码可以与std::cout以及一起工作时,我感到非常困惑std::ofstream,但例如,std::ostringstream即使所有这些都继承自基类std::ostream

facet正常构造,代码编译,它不会抛出任何异常......只是没有std::codecvt调用任何成员函数。

对我来说,这是非常令人困惑的,我不得不花很多时间弄清楚这std::codecvt不会对非文件 I/O 流做任何事情。

是否有任何原因std::codecvt没有被继承的所有类使用std::ostream

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

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

通过 std::basic_fstream 执行的所有文件 I/O 操作使用流中灌输的语言环境的 std::codecvt<CharT, char, std::mbstate_t> 方面。

来源: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";
}
Run Code Online (Sandbox Code Playgroud)

我会invocation_counter在流后增加std::ostringstream,但事实并非如此。


更新 2:

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

std::wbuffer_convert是类型流缓冲区的包装器, std::basic_streambuf<char>使其具有 std::basic_streambuf<Elem>. 执行的所有 I/O 都 std::wbuffer_convert经历了由构面 Codecvt 定义的字符转换。[...]

此类模板使 的隐式字符转换功能std::basic_filebuf可用于任何 std::basic_streambuf.

通过这种方式,我可以将一个方面应用于 a 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());
Run Code Online (Sandbox Code Playgroud)

但是,我失去了使用流操作符连接 facet 的能力<<

这让我更加困惑,为什么std::codecvt所有输出流都没有隐式使用。所有输出流都继承自std::basic_streambuf其接口适用于 using std::codecvt,它只是使用一个输入和一个输出字符序列,完全在std::basic_streambuf.

那么为什么解析std::codecvt实现 instd::basic_filebuf而不是std::basic_streambuf?毕竟std::basic_filebuf继承std::basic_streambuf...

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

Jan*_*iel 5

std::codecvt方面最初旨在处理磁盘内存字符表示之间的 I/O 转换。引用39.4.6Bjarne Stroustrup 的《C++ 编程语言》第四版的一段话:

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

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

为什么 std::codecvt 仅由文件 I/O 流使用?

文档中我们看到:

std::basic_fstream<CharT>通过使用std::codecvt<CharT, char, std::mbstate_t>流中注入的语言环境的方面执行的所有文件 I/O 操作。

然后回答了为什么std::ofstream(使用基于文件的流缓冲区)和std::cout链接到标准输出 FILE 流)调用的问题std::codecvt

现在,要使用高级std::ostream接口,您需要提供底层streambuf. 提供std::ofstream了 afilebufstd::ostringstream提供了 a stringbuf(与 的使用无关std::codecvt)。请参阅Streams上的这篇文章,其中还重点介绍了以下内容:

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

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

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

使用时,std::wbuffer_convert您可以将原件std::ostringstream用 a包裹起来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";
Run Code Online (Sandbox Code Playgroud)

与此处的完整示例一起,输出将是:

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
Run Code Online (Sandbox Code Playgroud)

结论

std::codecvt旨在用于磁盘和内存表示之间的转换。这就是为什么仅使用使用底层(例如和 )std::codecvt的流来调用实现的原因。但是,使用底层的流可以使用包装到实例中,然后该实例将调用底层。filebufstd::ofstreamstd::coutstringbufstd::wbuffer_convertstd::ostreamstd::codecvt