字符串视图 API
使用 std::string
的 C++ 字符串字段 API 显著地限制了内部 protobuf 实现及其演进。例如,mutable_string_field()
返回 std::string*
,这迫使我们使用 std::string
来存储字段。这使得它在 arena 上的交互变得复杂,并且我们必须维护 arena 捐赠状态以跟踪字符串有效负载分配是来自 arena 还是堆。
从长远来看,我们希望迁移我们所有的运行时和生成的 API,以接受 string_view
作为输入,并从访问器返回它们。本文档描述了截至 30.x 版本的迁移状态。
字符串字段访问器
作为 2023 版本的一部分,string_type
功能发布了一个 VIEW
选项,以允许逐步迁移到生成的 string_view
API。使用此功能将影响 string
和 bytes
字段的 C++ 生成代码。
与 ctype 的交互
在 2023 版本中,您仍然可以在字段级别指定 ctype
,同时您可以在文件或字段级别指定 string_type
。不允许在同一字段上同时指定两者。如果 string_type
在文件级别设置,则在字段上指定的 ctype
将优先。
除了 VIEW
选项外,string_type
的所有可能值都有一个对应的 ctype
值,该值的拼写相同并提供相同的行为。例如,两个枚举都具有 CORD
值。
在 2024 版本及更高版本中,将不再可能指定 ctype
。
生成的单一字段
对于 2023 版本中的任何这些字段定义
bytes foo = 1 [features.(pb.cpp).string_type=VIEW];
string foo = 1 [features.(pb.cpp).string_type=VIEW];
编译器将生成以下访问器方法
::absl::string_view foo() const
: 返回字段的当前值。如果未设置字段,则返回默认值。void clear_foo()
: 清除字段的值。调用此方法后,foo()
将返回默认值。bool has_foo()
: 如果设置了字段,则返回true
。void set_foo(::absl::string_view value)
: 设置字段的值。调用此方法后,has_foo()
将返回true
,并且foo()
将返回value
的副本。void set_foo(const string& value)
: 设置字段的值。调用此方法后,has_foo()
将返回true
,并且foo()
将返回value
的副本。void set_foo(string&& value)
: 设置字段的值,从传递的字符串移动。调用此方法后,has_foo()
将返回true
,并且foo()
将返回value
。void set_foo(const char* value)
: 使用 C 风格的 null 终止字符串设置字段的值。调用此方法后,has_foo()
将返回true
,并且foo()
将返回value
的副本。
生成的重复字段
对于任何这些字段定义
repeated string foo = 1 [features.(pb.cpp).string_type=VIEW];
repeated bytes foo = 1 [features.(pb.cpp).string_type=VIEW];
编译器将生成以下访问器方法
int foo_size() const
: 返回字段中当前元素的数量。::absl::string_view foo(int index) const
: 返回给定从零开始的索引处的元素。使用超出[0, foo_size()-1]
范围的索引调用此方法会产生未定义的行为。void set_foo(int index, ::absl::string_view value)
: 设置给定从零开始的索引处的元素的值。void set_foo(int index, const string& value)
: 设置给定从零开始的索引处的元素的值。void set_foo(int index, string&& value)
: 设置给定从零开始的索引处的元素的值,从传递的字符串移动。void set_foo(int index, const char* value)
: 使用 C 风格的 null 终止字符串设置给定从零开始的索引处的元素的值。void add_foo(::absl::string_view value)
: 将一个新元素附加到字段的末尾,并赋予给定的值。void add_foo(const string& value)
: 将一个新元素附加到字段的末尾,并赋予给定的值。void add_foo(string&& value)
: 将一个新元素附加到字段的末尾,从传递的字符串移动。void add_foo(const char* value)
: 使用 C 风格的 null 终止字符串将一个新元素附加到字段的末尾。void clear_foo()
: 从字段中删除所有元素。调用此方法后,foo_size()
将返回零。const RepeatedPtrField<string>& foo() const
: 返回存储字段元素的底层RepeatedPtrField
。此容器类提供类似 STL 的迭代器和其他方法。RepeatedPtrField<string>* mutable_foo()
: 返回指向存储字段元素的可变底层RepeatedPtrField
的指针。此容器类提供类似 STL 的迭代器和其他方法。
生成的 Oneof 字段
对于任何这些 oneof 字段定义
oneof example_name {
string foo = 1 [features.(pb.cpp).string_type=VIEW];
...
}
oneof example_name {
bytes foo = 1 [features.(pb.cpp).string_type=VIEW];
...
}
编译器将生成以下访问器方法
bool has_foo() const
: 如果 oneof case 是kFoo
,则返回true
。::absl::string_view foo() const
: 如果 oneof case 是kFoo
,则返回字段的当前值。否则,返回默认值。void set_foo(::absl::string_view value)
:- 如果设置了同一 oneof 中的任何其他 oneof 字段,则调用
clear_example_name()
。 - 设置此字段的值并将 oneof case 设置为
kFoo
。 has_foo()
将返回true
,foo()
将返回value
的副本,并且example_name_case()
将返回kFoo
。
- 如果设置了同一 oneof 中的任何其他 oneof 字段,则调用
void set_foo(const string& value)
: 类似于第一个set_foo()
,但从const string
引用复制。void set_foo(string&& value)
: 类似于第一个set_foo()
,但从传递的字符串移动。void set_foo(const char* value)
: 类似于第一个set_foo()
,但从 C 风格的 null 终止字符串复制。void clear_foo()
:- 如果 oneof case 不是
kFoo
,则不会进行任何更改。 - 如果 oneof case 是
kFoo
,则释放字段并清除 oneof case。has_foo()
将返回false
,foo()
将返回默认值,并且example_name_case()
将返回EXAMPLE_NAME_NOT_SET
。
- 如果 oneof case 不是
枚举名称助手
从 2024 版本开始,引入了一个新功能 enum_name_uses_string_view
,默认值为 true。除非禁用,否则对于像这样的枚举
enum Foo {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
除了 Foo
枚举之外,协议缓冲区编译器还将生成以下新函数,以及标准的 生成代码
::absl::string_view Foo_Name(int value)
: 返回给定数值的名称。如果不存在这样的值,则返回空字符串。如果多个值具有此数字,则返回定义的第一个值。在上面的示例中,Foo_Name(5)
将返回VALUE_B
。
这可以通过添加像这样的功能覆盖来恢复到旧的行为。
enum Foo {
option features.(pb.cpp).enum_name_uses_string_view = false;
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
在这种情况下,名称助手将切换回 const string& Foo_Name(int value)
。