string_view 格式化流输出的实现
Implementation of string_view formatted stream ouput
在实现 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 是这样做的:(发现 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、 但它大量使用内部函数 来进行格式化。我需要自己重新实现所有格式吗?
据我所知,您必须自己处理。
幸运的是,您需要为类似字符串的项目进行的格式化非常少——如果需要,主要是在字符串之前或之后插入填充。
- 要确定是否需要填充,您需要使用
ios_base::width()
检索流的当前字段。
- 要确定是在写出字符串之前还是之后插入它,您需要使用
ios_base::fmtflags()
检索 left/right 标志。
- 要找出 插入什么 作为填充,您可以调用
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
对象。如图所示,创建和使用它非常简单,但毫无疑问,删除它至少会快一点点。
在实现 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 是这样做的:(发现 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、 但它大量使用内部函数 来进行格式化。我需要自己重新实现所有格式吗?
据我所知,您必须自己处理。
幸运的是,您需要为类似字符串的项目进行的格式化非常少——如果需要,主要是在字符串之前或之后插入填充。
- 要确定是否需要填充,您需要使用
ios_base::width()
检索流的当前字段。 - 要确定是在写出字符串之前还是之后插入它,您需要使用
ios_base::fmtflags()
检索 left/right 标志。 - 要找出 插入什么 作为填充,您可以调用
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
对象。如图所示,创建和使用它非常简单,但毫无疑问,删除它至少会快一点点。