1

我正在尝试使用现代字符串处理方法(如std::string_viewGSL'sstring_span)与将字符串作为空终止的 C API(DBus)进行交互const char*,例如

DBusMessage* dbus_message_new_method_call(
    const char* destination,
    const char* path,
    const char* iface,
    const char* method 
    )

string_view并且string_span不保证它们的内容是空终止的——因为跨度是(char* start, ptrdiff_t length)对的,这在很大程度上是重点。但是 GSL 也提供了一个zstring_view,它保证是空终止的。周围的评论zstring_span表明它是专为处理遗留 API 和 C API 而设计的,但我一开始使用它就遇到了几个症结:

  1. 将字符串文字表示为 astring_span很简单:

    cstring_span<> bar = "easy peasy";
    

    但是将一个表示为 azstring_span需要您将文字包装在辅助函数中:

    czstring_span<> foo = ensure_z("odd");
    

    这使得声明更加嘈杂,而且文字(保证以 null 结尾)不能隐式转换为 a 似乎也很奇怪zstring_spanensure_z()也不是constexpr,不像string_span.

  2. 有一个类似的奇怪之处std::string,它可以隐式转换为string_span,但不是zstring_span,尽管std::string::data()自 C++11 以来已保证返回一个以空值结尾的序列。同样,您必须致电ensure_z()

    zstring_span<> to_zspan(std::string& s) { return ensure_z(s); }
    
  3. 似乎存在一些 const 正确性问题。上述工作,但

    czstring_span<> to_czspan(const std::string& s) { return ensure_z(s); }
    

    编译失败,出现关于无法转换为的span<char, ...>错误span<const char, ...>

  4. 这一点比其他点小,但返回 a 的成员函数char*(您可以将其提供给像 DBus 这样的 C API)被调用assume_z()。当构造函数期望一个以空值结尾的范围时,会假设什么?zstring_span

如果zstring_span设计为“将零终止跨度转换为遗留字符串”,为什么在这里使用它看起来如此麻烦?我在滥用它吗?有什么我忽略的吗?

4

2 回答 2

1
  1. 字面量(保证以空结尾)不能隐式转换为zstring_span

字符串文字的类型为const char[...]。类型中没有任何信息表明此const char数组是空终止字符串。这是其他一些具有相同类型的代码,但没有空终止,ensure_z会很快失败。

const char foo_arr[4]{ 'o', 'd', 'd', '-' };
ensure_z(foo_arr);

"foo"和都是foo_arrtype const char[4],但只有字符串文字是空终止的,而foo_arr不是。

请注意,您的组合ensure_zczstring_span<>编译,但它不起作用。ensure_z仅返回没有终止空字节的字符串。当您将其传递给czstring_span<>构造函数时,构造函数将无法搜索空字节(被 截断ensure_z)。

您需要将字符串文字转换为跨度并将其传递给构造函数:

czstring_span<> foo = ensure_span("odd");
  1. 有一个类似的奇怪之处std::string,它可以隐式转换为string_span,但不是zstring_span

好点子。有一个构造函数string_span采用 a std::string,但zstring_span只有一个构造函数采用内部实现类型 a span<char>。因为span有一个构造函数采用具有.data().size()-的“容器”std::string实现。更糟糕的是:以下代码编译但不会工作:

zstring_span<> to_zspan(std::string& s) { return zstring_span<>{s}; }

您应该考虑在 GSL 存储库中提交问题以使类对齐。我不确定隐式转换是否是个好主意,所以我更喜欢它是zstring_span如何完成的string_span

  1. 似乎存在一些 const 正确性问题。

同样在这里我的第一个想法是czstring_span<> to_czspan(const std::string& s) { return czstring_span<>{s}; }编译但不起作用。另一种解决方案是ensure_cz返回一个新函数span<const char, ...>。您应该考虑提出问题。

  1. assume_z()

的存在empty()和代码as_string_span()表明该类旨在能够处理空字符串跨度。在这种情况下,as_string_span将始终返回不终止空字节ensure_z的字符串,将返回带有终止空字节的字符串,如果为空则失败,并assume_z假定!empty()并返回带有终止空字节的字符串。

但是唯一的构造函数采用非空字符跨度,所以empty()永远不可能true。我刚刚创建了一个PR来解决这些不一致的问题。如果您认为应该更改更多内容,请考虑提交问题。

如果zstring_span设计为“将零终止跨度转换为遗留字符串”,为什么在这里使用它看起来如此麻烦?我在滥用它吗?有什么我忽略的吗?

在我更喜欢的纯 C++ 代码中std::string_viewzstring_span仅用于 C 互操作,这限制了它的使用。当然,您必须了解指南和指南支持库。鉴于我敢打赌,zstring_span它很少被使用,并且您是极少数深入研究它的人之一。

于 2020-02-28T10:13:55.580 回答
0

它“麻烦”部分是因为它是有意的。

这个:

zstring_span<> to_zspan(std::string& s) { return ensure_z(s); }

不是安全操作。为什么?因为虽然确实s是 NUL 终止,但实际完全有可能s包含内部 NUL 字符。这是你可以做的合法的事情std::string,但zstring_span无论谁接受它都无法处理。他们会截断字符串。

相比之下,string_span/view从这个角度来看,转换是安全的。此类字符串的消费者接受一个大小的字符串,因此可以处理嵌入的 NUL。

因为zstring_span转换是不安全的,所以应该有一些明确的符号表明正在做一些可能不安全的事情。ensure_z表示该显式符号。

另一个问题是 C++ 没有机制来区分文字字符串参数和任何旧的const char*const char[]参数之间的区别。由于bareconst char*可能是也可能不是字符串文字,因此您必须假设它不是,因此使用更详细的转换。

此外,C++ 字符串文字可以包含嵌入的 NUL 字符,因此上述推理适用。

这个const问题似乎是一个代码错误,您可能应该这样归档它。

于 2019-07-03T19:19:53.240 回答