【问题标题】:Implementation of string_view formatted stream ouputstring_view 格式化流输出的实现
【发布时间】:2016-09-23 05:55:04
【问题描述】:

在实现 C++1z 的 std::basic_string_view 以在较旧的编译器上使用它时,我遇到了 stream output operator overload 的问题。 基本上,它必须输出 string_view 引用的内容,同时不依赖任何空终止符存在(因为 string_view 不保证是空终止的)。

通常,为operator<< 编写重载非常容易,因为您可以依赖已经存在的重载,因此不需要使用哨兵对象as mentioned in this question on SO

但在这种情况下,operator<< 没有预定义的重载,采用字符指针和长度 (obviously)。因此,我在当前的实现中创建了一个临时的 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 是这样操作的:(found 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 的实现驻留在in this file但它大量使用内部函数来进行格式化。我需要自己重新实现所有格式吗?

【问题讨论】:

  • 格式化是关于适当的文本对齐的填充(如表格列格式化)——这是很少需要的,所以这一切都归结为你的需要:如果你正在编写一个库,那么有人会来回来困扰你;如果您的程序不关心它,您可以忽略它。此外,填充代码看起来并不复杂也不长——你也可以添加它。
  • @Alexey Guseynov 由于 string_view 的长度是运行时值,我不知道如何为此使用自定义特征类型,因为它的构造(我可以用来提供我的运行时长度值)发生在你提到的operator&lt;&lt; 重载中,我无法控制。
  • 我没有看到任何真正的方法可以避免自己处理格式。可能看起来很奇怪(对我来说有点),但有时这就是生活。幸运的是,字符串的格式非常少。
  • 您可以使用ios_base::width() 检索当前宽度。您还需要查看leftright 和(如果没有记错的话)fixed 以确定在何处插入填充,以及是否截断到字段宽度(使用ios_base::fmtflags() 检索) .嗯...哦,如果您要进行填充,您还需要读取当前的填充字符(使用ios_base::fill())。

标签: c++ c++17 string-view


【解决方案1】:

据我所知,您必须自己处理。

幸运的是,您需要为类似字符串的项目执行的格式设置非常少——如果需要,主要是在字符串之前或之后插入填充。

  • 要确定是否需要填充,您需要使用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 const &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 对象。如图所示,创建和使用它非常简单,但毫无疑问,删除它至少会快一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多