9

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

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

然而,当我发现我的std::codecvt代码和.std::ostreamstd::coutstd::ofstreamstd::ostringstreamstd::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";
}

我会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.

这样我可以将一个方面应用于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());

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

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

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

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

4

1 回答 1

5

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

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

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

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

文档中我们看到:

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

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

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

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

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

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

使用时,您可以用 astd::wbuffer_convert包裹原件,如下所示:std::ostringstreamstd::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";

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

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::ofstream和之类的底层std::cout。但是,使用底层的流stringbuf可以使用包装std::wbuffer_convert到一个std::ostream实例中,然后该实例将调用底层std::codecvt

于 2021-10-13T12:03:36.797 回答