string_view格式流输出的实现

问题描述 投票:0回答:2

在实现 C++1z 的

std::basic_string_view
以在较旧的编译器上使用它时,我遇到了流输出运算符重载 的问题。 基本上,它必须输出
string_view
引用的内容,同时不依赖于任何存在的空终止符(因为
string_view
不保证以空终止符结尾)。

通常,为

operator<<
编写重载非常容易,因为您可以依赖已经存在的重载,因此不需要使用哨兵对象 ,如本问题中所提到的 SO

但在这种情况下,

operator<<
没有预定义的重载,它采用字符指针和长度(显然)。因此,我在当前的实现中创建了一个临时
std::string
实例:

template< typename TChar, typename TTraits >
auto operator<<(::std::basic_ostream<TChar, TTraits>& p_os, basic_string_view<TChar, TTraits> p_v)
    -> ::std::basic_ostream<TChar, TTraits>&
{
    p_os << p_v.to_string(); // to_string() returns a ::std::string.
    return p_os;
}

这可行,但我真的不喜欢这样的事实:我必须创建一个临时的

std::string
实例,因为这需要冗余地复制数据和潜在的使用动态内存。至少在我看来,这违背了使用轻量级引用类型的目的。

所以我的问题是:

在没有开销的情况下为我的 string_view 实现正确格式化输出的最佳方法是什么?


在研究时,我发现 LLVM 是这样做的:(找到here

// [string.view.io]
template<class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, basic_string_view<_CharT, _Traits> __sv)
{
    return _VSTD::__put_character_sequence(__os, __sv.data(), __sv.size());
}

__put_character_sequence
的实现位于在此文件中但它大量使用内部函数来进行格式化。我需要自己重新实现所有格式吗?

c++ c++17 string-view
2个回答
8
投票

据我所知,你必须自己处理这个问题。

幸运的是,您需要对类似字符串的项目进行的格式化相当少——主要是根据需要在字符串之前或之后插入填充。

  • 要确定是否需要填充,您需要使用
    ios_base::width()
    检索流的当前字段。
  • 要确定是在写出字符串之前还是之后插入该字符串,您需要使用
    ios_base::fmtflags()
    检索左/右标志。
  • 要弄清楚要插入什么作为填充,您可以调用 ios_base::fill()
  • 最后,我相信您需要检查
  • fixed
     标志 - 如果没记错的话,设置它后,如果字符串比当前字段宽度长,您需要截断字符串。
因此(使用

string_view

 的超简化实现),代码可能如下所示:

#include <iostream> #include <iomanip> #include <ios> #include <sstream> class string_view { char const *data; size_t len; public: string_view(char const *data, size_t len) : data(data), len(len) {} friend std::ostream &operator<<(std::ostream &os, string_view sv) { std::ostream::sentry s{ os }; if (s) { auto fill = os.fill(); auto width = os.width(); bool left = os.flags() & std::ios::left; bool right = os.flags() & std::ios::right; bool fixed = os.flags() & std::ios::fixed; auto pad = [&](size_t width) { while (width--) os.put(fill); }; if (sv.len < width) { auto padding_len = width - sv.len; if (right) pad(padding_len); os.write(sv.data, sv.len); if (left) pad(padding_len); } else { os.write(sv.data, fixed ? width : sv.len); } } os.width(0); return os; } }; #ifdef TEST void check(std::stringstream &a, std::stringstream &b) { static int i; ++i; if (a.str() != b.str()) { std::cout << "Difference in test:" << i << "\n"; std::cout << "\"" << a.str() << "\"\n"; std::cout << "\"" << b.str() << "\"\n"; } a.seekp(0); b.seekp(0); } int main() { char string[] = "Now is the time for every good man to come to the aid of Jerry."; std::stringstream test1; std::stringstream test2; test1 << string_view(string, 3); test2 << std::string(string, 3); check(test1, test2); test1 << string_view(string + 4, 2); test2 << string_view(string + 4, 2); check(test1, test2); test1 << std::setw(10) << std::left << string_view(string, 6); test2 << std::setw(10) << std::left << std::string(string, 6); check(test1, test2); test1 << std::setw(10) << std::right << string_view(string, 6); test2 << std::setw(10) << std::right << std::string(string, 6); check(test1, test2); test1 << std::setw(10) << std::right << string_view(string, sizeof(string)); test2 << std::setw(10) << std::right << std::string(string, sizeof(string)); check(test1, test2); test1 << std::setw(10) << std::right << std::fixed << string_view(string, sizeof(string)); test2 << std::setw(10) << std::right << std::fixed << std::string(string, sizeof(string)); check(test1, test2); } #endif
哦——还有一个细节。由于我们只写入流,而不是直接写入底层缓冲区,因此我认为在这种情况下我们可能实际上不需要创建 

sentry 对象。如图所示,创建和使用它非常简单,但毫无疑问,删除它后至少会快一些。


在我看来,您应该能够让插入运算符依赖于 std::ostream::write 。比如:

1
投票
template<class _CharT, class _Traits> basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& __os, basic_string_view<_CharT, _Traits> __sv) { __os.write(__sv.data(), __sv.size()); return __os; }


© www.soinside.com 2019 - 2024. All rights reserved.