Add arena enabled copy construction.

Arena enabled copy construction provides efficiency gains for the rather common construct where a message is currently copied by first creating a default initialized instance through the regular (arena) constructor, and then recursively filled from the source message using 'MergeFrom' or 'CopyFrom'.

Arena enabled copy construction is feature gated in port_def.inc and currently not enabled by default.

PiperOrigin-RevId: 558854125
pull/13622/head
Martijn Vels 1 year ago committed by Copybara-Service
parent ef58724805
commit a2b347087e
  1. 60
      src/google/protobuf/compiler/cpp/field.cc
  2. 81
      src/google/protobuf/compiler/cpp/field.h
  3. 37
      src/google/protobuf/compiler/cpp/field_generators/cord_field.cc
  4. 28
      src/google/protobuf/compiler/cpp/field_generators/enum_field.cc
  5. 3
      src/google/protobuf/compiler/cpp/field_generators/map_field.cc
  6. 60
      src/google/protobuf/compiler/cpp/field_generators/message_field.cc
  7. 27
      src/google/protobuf/compiler/cpp/field_generators/primitive_field.cc
  8. 43
      src/google/protobuf/compiler/cpp/field_generators/string_field.cc
  9. 9
      src/google/protobuf/compiler/cpp/file.cc
  10. 5
      src/google/protobuf/compiler/cpp/helpers.cc
  11. 575
      src/google/protobuf/compiler/cpp/message.cc
  12. 12
      src/google/protobuf/compiler/cpp/message.h
  13. 7
      src/google/protobuf/message_lite.h

@ -121,6 +121,7 @@ FieldGeneratorBase::FieldGeneratorBase(const FieldDescriptor* descriptor,
const Options& options,
MessageSCCAnalyzer* scc)
: descriptor_(descriptor), options_(options) {
bool is_repeated_or_map = descriptor->is_repeated();
should_split_ = ShouldSplit(descriptor, options);
is_oneof_ = descriptor->real_containing_oneof() != nullptr;
switch (descriptor->cpp_type()) {
@ -132,26 +133,71 @@ FieldGeneratorBase::FieldGeneratorBase(const FieldDescriptor* descriptor,
case FieldDescriptor::CPPTYPE_FLOAT:
case FieldDescriptor::CPPTYPE_DOUBLE:
case FieldDescriptor::CPPTYPE_BOOL:
is_trivial_ = !(descriptor->is_repeated() || descriptor->is_map());
has_trivial_value_ = is_trivial_;
is_trivial_ = has_trivial_value_ = !is_repeated_or_map;
has_default_constexpr_constructor_ = is_repeated_or_map;
break;
case FieldDescriptor::CPPTYPE_STRING:
is_string_ = true;
string_type_ = descriptor->options().ctype();
is_inlined_ = IsStringInlined(descriptor, options);
is_bytes_ = descriptor->type() == FieldDescriptor::TYPE_BYTES;
has_default_constexpr_constructor_ = is_repeated_or_map;
break;
case FieldDescriptor::CPPTYPE_MESSAGE:
is_message_ = true;
is_group_ = descriptor->type() == FieldDescriptor::TYPE_GROUP;
is_foreign_ = IsCrossFileMessage(descriptor);
is_lazy_ = IsLazy(descriptor, options, scc);
is_weak_ = IsImplicitWeakField(descriptor, options, scc);
if (!(descriptor->is_repeated() || descriptor->is_map())) {
has_trivial_value_ = !is_lazy_;
}
is_lazy_ = IsLazy(descriptor, options, scc);
has_trivial_value_ = !(is_repeated_or_map || is_lazy_);
has_default_constexpr_constructor_ = is_repeated_or_map || is_lazy_;
break;
}
has_trivial_zero_default_ = CanInitializeByZeroing(descriptor, options, scc);
}
void FieldGeneratorBase::GenerateMemberConstexprConstructor(
io::Printer* p) const {
ABSL_CHECK(!descriptor_->is_extension());
if (descriptor_->is_repeated()) {
p->Emit("$name$_{}");
} else {
p->Emit({{"default", DefaultValue(options_, descriptor_)}},
"$name$_{$default$}");
}
}
void FieldGeneratorBase::GenerateMemberConstructor(io::Printer* p) const {
ABSL_CHECK(!descriptor_->is_extension());
if (descriptor_->is_map()) {
p->Emit("$name$_{visibility, arena}");
} else if (descriptor_->is_repeated()) {
if (ShouldSplit(descriptor_, options_)) {
p->Emit("$name$_{}"); // RawPtr<Repeated>
} else {
p->Emit("$name$_{visibility, arena}");
}
} else {
p->Emit({{"default", DefaultValue(options_, descriptor_)}},
"$name$_{$default$}");
}
}
void FieldGeneratorBase::GenerateMemberCopyConstructor(io::Printer* p) const {
ABSL_CHECK(!descriptor_->is_extension());
if (descriptor_->is_repeated()) {
p->Emit("$name$_{visibility, arena, from.$name$_}");
} else {
p->Emit("$name$_{from.$name$_}");
}
}
void FieldGeneratorBase::GenerateOneofCopyConstruct(io::Printer* p) const {
ABSL_CHECK(!descriptor_->is_extension()) << "Not supported";
ABSL_CHECK(!descriptor_->is_repeated()) << "Not supported";
ABSL_CHECK(!descriptor_->is_map()) << "Not supported";
p->Emit("$field$ = from.$field$;\n");
}
void FieldGeneratorBase::GenerateAggregateInitializer(io::Printer* p) const {
@ -275,6 +321,8 @@ void InlinedStringVars(const FieldDescriptor* field, const Options& opts,
int32_t index = *idx / 32;
std::string mask = absl::StrFormat("0x%08xu", 1u << (*idx % 32));
vars.emplace_back("inlined_string_index", index);
vars.emplace_back("inlined_string_mask", mask);
absl::string_view array = IsMapEntryMessage(field->containing_type())
? "_inlined_string_donated_"

@ -80,6 +80,10 @@ class FieldGeneratorBase {
// trivial, or a (raw) pointer value to a singular, non lazy message.
bool has_trivial_value() const { return has_trivial_value_; }
// Returns true if the provided field has a trivial zero default.
// I.e., the field can be initialized with `memset(&field, 0, sizeof(field))`
bool has_trivial_zero_default() const { return has_trivial_zero_default_; }
// Returns true if the field is a singular or repeated message.
// This includes group message types. To explicitly check if a message
// type is a group type, use the `is_group()` function,
@ -113,6 +117,12 @@ class FieldGeneratorBase {
// allocated. Applies to string and message value.
bool is_inlined() const { return is_inlined_; }
// Returns true if this field has an appropriate default constexpr
// constructor. i.e., there is no need for an explicit initializer.
bool has_default_constexpr_constructor() const {
return has_default_constexpr_constructor_;
}
virtual std::vector<io::Printer::Sub> MakeVars() const { return {}; }
virtual void GeneratePrivateMembers(io::Printer* p) const = 0;
@ -150,6 +160,32 @@ class FieldGeneratorBase {
<< descriptor_->cpp_type_name();
}
// Generates constexpr member initialization code, e.g.: `foo_{5}`.
// The default implementation generates the following code:
// - repeated fields and maps: `field_{}`
// - all other fields: `field_{<default value>}`
virtual void GenerateMemberConstexprConstructor(io::Printer* p) const;
// Generates member initialization code, e.g.: `foo_(5)`.
// The default implementation generates the following code:
// - repeated fields and maps: `field_{visibility, arena}`
// - split repeated fields (RawPtr): `field_{}`
// - all other fields: `field_{<default value>}`
virtual void GenerateMemberConstructor(io::Printer* p) const;
// Generates member copy initialization code, e.g.: `foo_(5)`.
// The default implementation generates the following code:
// - repeated fields and maps: `field_{visibility, arena, from.field_}`
// - all other fields: `field_{from.field_}`
virtual void GenerateMemberCopyConstructor(io::Printer* p) const;
// Generates 'placement new' copy construction code used to
// explicitly copy initialize oneof field values.
// The default implementation checks the current field to not be repeated,
// an extension or a map, and generates the following code:
// - `field_ = from.field_`
virtual void GenerateOneofCopyConstruct(io::Printer* p) const;
virtual void GenerateAggregateInitializer(io::Printer* p) const;
virtual void GenerateConstexprAggregateInitializer(io::Printer* p) const;
@ -170,15 +206,16 @@ class FieldGeneratorBase {
}
protected:
// TODO(b/245791219): Remove these members and make this a pure interface.
const FieldDescriptor* descriptor_;
const Options& options_;
MessageSCCAnalyzer* scc_;
absl::flat_hash_map<absl::string_view, std::string> variables_;
private:
bool should_split_ = false;
bool is_trivial_ = false;
bool has_trivial_value_ = false;
bool has_trivial_zero_default_ = false;
bool is_message_ = false;
bool is_group_ = false;
bool is_string_ = false;
@ -189,6 +226,7 @@ class FieldGeneratorBase {
bool is_weak_ = false;
bool is_oneof_ = false;
FieldOptions::CType string_type_ = FieldOptions::STRING;
bool has_default_constexpr_constructor_ = false;
};
inline FieldGeneratorBase::~FieldGeneratorBase() = default;
@ -220,6 +258,27 @@ class FieldGenerator {
FieldGenerator(FieldGenerator&&) = default;
FieldGenerator& operator=(FieldGenerator&&) = default;
// Properties: see FieldGeneratorBase for documentation
bool should_split() const { return impl_->should_split(); }
bool is_trivial() const { return impl_->is_trivial(); }
bool has_trivial_value() const { return impl_->has_trivial_value(); }
bool has_trivial_zero_default() const {
return impl_->has_trivial_zero_default();
}
bool is_message() const { return impl_->is_message(); }
bool is_group() const { return impl_->is_group(); }
bool is_weak() const { return impl_->is_weak(); }
bool is_lazy() const { return impl_->is_lazy(); }
bool is_foreign() const { return impl_->is_foreign(); }
bool is_string() const { return impl_->is_string(); }
bool is_bytes() const { return impl_->is_bytes(); }
FieldOptions::CType string_type() const { return impl_->string_type(); }
bool is_oneof() const { return impl_->is_oneof(); }
bool is_inlined() const { return impl_->is_inlined(); }
bool has_default_constexpr_constructor() const {
return impl_->has_default_constexpr_constructor();
}
// Prints private members needed to represent this field.
//
// These are placed inside the class definition.
@ -236,6 +295,26 @@ class FieldGenerator {
impl_->GenerateStaticMembers(p);
}
void GenerateMemberConstructor(io::Printer* p) const {
auto vars = PushVarsForCall(p);
impl_->GenerateMemberConstructor(p);
}
void GenerateMemberCopyConstructor(io::Printer* p) const {
auto vars = PushVarsForCall(p);
impl_->GenerateMemberCopyConstructor(p);
}
void GenerateOneofCopyConstruct(io::Printer* p) const {
auto vars = PushVarsForCall(p);
impl_->GenerateOneofCopyConstruct(p);
}
void GenerateMemberConstexprConstructor(io::Printer* p) const {
auto vars = PushVarsForCall(p);
impl_->GenerateMemberConstexprConstructor(p);
}
// Generates declarations for all of the accessor functions related to this
// field.
//

@ -68,7 +68,7 @@ void SetCordVariables(
(*variables)["default_variable_field"] = MakeDefaultFieldName(descriptor);
(*variables)["default_variable"] =
descriptor->default_value_string().empty()
? absl::StrCat(ProtobufNamespace(options),
? absl::StrCat("::", ProtobufNamespace(options),
"::internal::GetEmptyCordAlreadyInited()")
: absl::StrCat(
QualifiedClassName(descriptor->containing_type(), options),
@ -88,7 +88,9 @@ class CordFieldGenerator : public FieldGeneratorBase {
void GenerateMergingCode(io::Printer* printer) const override;
void GenerateSwappingCode(io::Printer* printer) const override;
void GenerateConstructorCode(io::Printer* printer) const override;
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
void GenerateDestructorCode(io::Printer* printer) const override;
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void GenerateArenaDestructorCode(io::Printer* printer) const override;
void GenerateSerializeWithCachedSizesToArray(
io::Printer* printer) const override;
@ -99,6 +101,37 @@ class CordFieldGenerator : public FieldGeneratorBase {
ArenaDtorNeeds NeedsArenaDestructor() const override {
return ArenaDtorNeeds::kRequired;
}
void GenerateMemberConstexprConstructor(io::Printer* p) const override {
if (descriptor_->default_value_string().empty()) {
p->Emit("$name$_{}");
} else {
p->Emit({{"Split", ShouldSplit(descriptor_, options_) ? "Split::" : ""}},
"$name$_{::absl::strings_internal::MakeStringConstant("
"$classname$::Impl_::$Split$_default_$name$_func_{})}");
}
}
void GenerateMemberConstructor(io::Printer* p) const override {
auto vars = p->WithVars(variables_);
if (descriptor_->default_value_string().empty()) {
p->Emit("$name$_{}");
} else {
p->Emit("$name$_{::absl::string_view($default$, $default_length$)}");
}
}
void GenerateMemberCopyConstructor(io::Printer* p) const override {
auto vars = p->WithVars(variables_);
p->Emit("$name$_{from.$name$_}");
}
void GenerateOneofCopyConstruct(io::Printer* p) const override {
auto vars = p->WithVars(variables_);
p->Emit(R"cc(
$field$ = ::$proto_ns$::Arena::Create<absl::Cord>(arena, *from.$field$);
)cc");
}
};
class CordOneofFieldGenerator : public CordFieldGenerator {
@ -224,6 +257,7 @@ void CordFieldGenerator::GenerateConstructorCode(io::Printer* printer) const {
}
}
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
void CordFieldGenerator::GenerateDestructorCode(io::Printer* printer) const {
Formatter format(printer, variables_);
if (should_split()) {
@ -233,6 +267,7 @@ void CordFieldGenerator::GenerateDestructorCode(io::Printer* printer) const {
}
format("$field$.~Cord();\n");
}
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void CordFieldGenerator::GenerateArenaDestructorCode(
io::Printer* printer) const {

@ -37,6 +37,7 @@
#include <vector>
#include "absl/log/absl_check.h"
#include "absl/log/absl_log.h"
#include "absl/memory/memory.h"
#include "absl/strings/substitute.h"
#include "google/protobuf/compiler/cpp/field.h"
@ -304,9 +305,11 @@ class RepeatedEnum : public FieldGeneratorBase {
$field_$.DeleteIfNotDefault();
)cc");
} else {
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
p->Emit(R"cc(
_internal_mutable_$name$()->~RepeatedField();
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}
}
@ -346,6 +349,31 @@ class RepeatedEnum : public FieldGeneratorBase {
}
}
void GenerateMemberConstexprConstructor(io::Printer* p) const override {
p->Emit("$name$_{}");
if (has_cached_size_) {
p->Emit(",\n_$name$_cached_byte_size_{0}");
}
}
void GenerateMemberConstructor(io::Printer* p) const override {
p->Emit("$name$_{visibility, arena}");
if (has_cached_size_) {
p->Emit(",\n_$name$_cached_byte_size_{0}");
}
}
void GenerateMemberCopyConstructor(io::Printer* p) const override {
p->Emit("$name$_{visibility, arena, from.$name$_}");
if (has_cached_size_) {
p->Emit(",\n_$name$_cached_byte_size_{0}");
}
}
void GenerateOneofCopyConstruct(io::Printer* p) const override {
ABSL_LOG(FATAL) << "Not supported";
}
void GenerateCopyConstructorCode(io::Printer* p) const override {
if (should_split()) {
p->Emit(R"cc(

@ -182,10 +182,11 @@ class Map : public FieldGeneratorBase {
)cc");
return;
}
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
p->Emit(R"cc(
$field_$.~$MapField$();
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}
void GeneratePrivateMembers(io::Printer* p) const override;

@ -138,6 +138,24 @@ class SingularMessage : public FieldGeneratorBase {
void GenerateAggregateInitializer(io::Printer* p) const override;
void GenerateCopyAggregateInitializer(io::Printer* p) const override;
void GenerateMemberConstexprConstructor(io::Printer* p) const override {
p->Emit("$name$_{nullptr}");
}
void GenerateMemberConstructor(io::Printer* p) const override {
p->Emit("$name$_{nullptr}");
}
void GenerateMemberCopyConstructor(io::Printer* p) const override {
p->Emit("$name$_{CreateMaybeMessage<$Submsg$>(arena, *from.$name$_)}");
}
void GenerateOneofCopyConstruct(io::Printer* p) const override {
p->Emit(R"cc(
$field$ = CreateMaybeMessage<$Submsg$>(arena, *from.$field$);
)cc");
}
private:
friend class OneofMessage;
@ -448,20 +466,44 @@ void SingularMessage::GenerateDestructorCode(io::Printer* p) const {
using internal::cpp::HasHasbit;
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
void SingularMessage::GenerateCopyConstructorCode(io::Printer* p) const {
if (has_hasbit_) {
p->Emit(
"if ((from.$has_hasbit$) != 0) {\n"
" _this->$field_$ = new $Submsg$(*from.$field_$);\n"
"}\n");
p->Emit(R"cc(
if ((from.$has_hasbit$) != 0) {
_this->$field_$ = new $Submsg$(*from.$field_$);
}
)cc");
} else {
p->Emit(
"if (from._internal_has_$name$()) {\n"
" _this->$field_$ = new $Submsg$(*from.$field_$);\n"
"}\n");
p->Emit(R"cc(
if (from._internal_has_$name$()) {
_this->$field_$ = new $Submsg$(*from.$field_$);
}
)cc");
}
}
#else // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void SingularMessage::GenerateCopyConstructorCode(io::Printer* p) const {
if (has_hasbit_) {
p->Emit(R"cc(
if ((from.$has_hasbit$) != 0) {
_this->$field_$ = CreateMaybeMessage<$Submsg$>(arena, *from.$field_$);
}
)cc");
} else {
p->Emit(R"cc(
if (from._internal_has_$name$()) {
_this->$field_$ = CreateMaybeMessage<$Submsg$>(arena, *from.$field_$);
}
)cc");
}
}
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void SingularMessage::GenerateSerializeWithCachedSizesToArray(
io::Printer* p) const {
if (!is_group()) {
@ -958,7 +1000,9 @@ void RepeatedMessage::GenerateDestructorCode(io::Printer* p) const {
$field_$.DeleteIfNotDefault();
)cc");
} else {
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
p->Emit("$field_$.~$Weak$RepeatedPtrField();\n");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}
}

@ -353,9 +353,11 @@ class RepeatedPrimitive final : public FieldGeneratorBase {
$field_$.DeleteIfNotDefault();
)cc");
} else {
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
p->Emit(R"cc(
$field_$.~RepeatedField();
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}
}
@ -394,6 +396,31 @@ class RepeatedPrimitive final : public FieldGeneratorBase {
GenerateCacheSizeInitializer(p);
}
void GenerateMemberConstexprConstructor(io::Printer* p) const override {
p->Emit("$name$_{}");
if (HasCachedSize()) {
p->Emit(",\n_$name$_cached_byte_size_{0}");
}
}
void GenerateMemberConstructor(io::Printer* p) const override {
p->Emit("$name$_{visibility, arena}");
if (HasCachedSize()) {
p->Emit(",\n_$name$_cached_byte_size_{0}");
}
}
void GenerateMemberCopyConstructor(io::Printer* p) const override {
p->Emit("$name$_{visibility, arena, from.$name$_}");
if (HasCachedSize()) {
p->Emit(",\n_$name$_cached_byte_size_{0}");
}
}
void GenerateOneofCopyConstruct(io::Printer* p) const override {
ABSL_LOG(FATAL) << "Not supported";
}
void GeneratePrivateMembers(io::Printer* p) const override;
void GenerateAccessorDeclarations(io::Printer* p) const override;
void GenerateInlineAccessorDefinitions(io::Printer* p) const override;

@ -150,6 +150,45 @@ class SingularString : public FieldGeneratorBase {
)cc");
}
void GenerateMemberConstexprConstructor(io::Printer* p) const override {
if (is_inlined()) {
p->Emit("$name$_(nullptr, false)");
} else {
p->Emit(
"$name$_(\n"
" &$pbi$::fixed_address_empty_string,\n"
" ::_pbi::ConstantInitialized())");
}
}
void GenerateMemberConstructor(io::Printer* p) const override {
if (is_inlined()) {
p->Emit("$name$_{}");
} else if (EmptyDefault()) {
p->Emit("$name$_(arena)");
} else {
p->Emit("$name$_(arena, $default_variable_field$)");
}
}
void GenerateMemberCopyConstructor(io::Printer* p) const override {
if (is_inlined() || EmptyDefault()) {
p->Emit("$name$_(arena, from.$name$_)");
} else {
p->Emit("$name$_(arena, from.$name$_, $default_variable_name$)");
}
}
void GenerateOneofCopyConstruct(io::Printer* p) const override {
if (is_inlined() || EmptyDefault()) {
p->Emit("new (&$field$) decltype($field$){arena, from.$field$};\n");
} else {
p->Emit(
"new (&$field$) decltype($field$){arena, from.$field$,"
" $default_variable_field$};\n");
}
}
void GenerateStaticMembers(io::Printer* p) const override;
void GenerateAccessorDeclarations(io::Printer* p) const override;
void GenerateInlineAccessorDefinitions(io::Printer* p) const override;
@ -627,12 +666,14 @@ void SingularString::GenerateCopyConstructorCode(io::Printer* p) const {
void SingularString::GenerateDestructorCode(io::Printer* p) const {
if (is_inlined()) {
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
// Explicitly calls ~InlinedStringField as its automatic call is disabled.
// Destructor has been implicitly skipped as a union.
ABSL_DCHECK(!should_split());
p->Emit(R"cc(
$field_$.~InlinedStringField();
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
return;
}
@ -757,9 +798,11 @@ class RepeatedString : public FieldGeneratorBase {
$field_$.DeleteIfNotDefault();
)cc");
} else {
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
p->Emit(R"cc(
_internal_mutable_$name$()->~RepeatedPtrField();
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}
}

@ -1421,6 +1421,15 @@ class FileGenerator::ForwardDeclarations {
template <>
$dllexport_decl $$class$* Arena::CreateMaybeMessage<$class$>(Arena*);
)cc");
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
if (!IsMapEntryMessage(c.second)) {
p->Emit({{"class", QualifiedClassName(c.second, options)}}, R"cc(
template <>
$dllexport_decl $$class$* Arena::CreateMaybeMessage<$class$>(
Arena*, const $class$& from);
)cc");
}
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
}
}
}

@ -322,6 +322,11 @@ const char kThinSeparator[] =
bool CanInitializeByZeroing(const FieldDescriptor* field,
const Options& options,
MessageSCCAnalyzer* scc_analyzer) {
static_assert(
std::numeric_limits<float>::is_iec559 &&
std::numeric_limits<double>::is_iec559,
"proto / abseil requires iec559, which has zero initialized floats.");
if (field->is_repeated() || field->is_extension()) return false;
switch (field->cpp_type()) {
case FieldDescriptor::CPPTYPE_ENUM:

@ -1278,17 +1278,25 @@ void MessageGenerator::GenerateClassDefinition(io::Printer* p) {
"explicit PROTOBUF_CONSTEXPR "
"$classname$(::$proto_ns$::internal::ConstantInitialized);\n"
"\n"
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
"$classname$(::$proto_ns$::Arena* arena, const $classname$& from);\n"
"inline $classname$(const $classname$& from)\n"
" : $classname$(nullptr, from) {}\n"
#else // PROTOBUF_EXPLICIT_CONSTRUCTORS
"$classname$(const $classname$& from);\n"
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
"$classname$($classname$&& from) noexcept\n"
" : $classname$() {\n"
" *this = ::std::move(from);\n"
"}\n"
"\n"
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
"inline $classname$(::$proto_ns$::Arena* arena,"
" const $classname$& from)\n"
" : $classname$(arena) {\n"
" MergeFrom(from);\n"
"}\n"
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
"inline $classname$& operator=(const $classname$& from) {\n"
" CopyFrom(from);\n"
" return *this;\n"
@ -1717,6 +1725,10 @@ void MessageGenerator::GenerateClassDefinition(io::Printer* p) {
sizeof_has_bits, "> _has_bits_;\n");
format(
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
"friend class ::$proto_ns$::MessageLite;\n"
"friend class ::$proto_ns$::Arena;\n"
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
"template <typename T> friend class "
"::$proto_ns$::Arena::InternalHelper;\n"
"typedef void InternalArenaConstructable_;\n"
@ -1728,9 +1740,25 @@ void MessageGenerator::GenerateClassDefinition(io::Printer* p) {
// alignment.
// (3) members assumed to align to 4 bytes.
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
format("struct $dllexport_decl $Impl_ {\n");
#else
format("struct Impl_ {\n");
#endif
format.Indent();
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
// TODO(b/290029568): check if/when there is a need for an outline dtor.
format(R"cc(
inline explicit constexpr Impl_(
::$proto_ns$::internal::ConstantInitialized) noexcept;
inline explicit Impl_($pbi$::InternalVisibility visibility,
::$proto_ns$::Arena* arena);
inline explicit Impl_($pbi$::InternalVisibility visibility,
::$proto_ns$::Arena* arena, const Impl_& from);
)cc");
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
// Members assumed to align to 8 bytes:
if (descriptor_->extension_range_count() > 0) {
@ -2227,6 +2255,8 @@ std::pair<size_t, size_t> MessageGenerator::GenerateOffsets(io::Printer* p) {
return std::make_pair(entries, offsets);
}
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
void MessageGenerator::GenerateSharedConstructorCode(io::Printer* p) {
if (HasSimpleBaseClass(descriptor_, options_)) return;
@ -2387,6 +2417,235 @@ void MessageGenerator::GenerateSharedConstructorCode(io::Printer* p) {
)cc");
}
#else // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void MessageGenerator::GenerateZeroInitFields(io::Printer* p) const {
using Iterator = decltype(optimized_order_.begin());
const FieldDescriptor* first = nullptr;
auto emit_pending_zero_fields = [&](Iterator end) {
if (first != nullptr) {
const FieldDescriptor* last = end[-1];
if (first != last) {
p->Emit({{"first", FieldName(first)},
{"last", FieldName(last)},
{"Impl", "Impl_"},
{"impl", "_impl_"}},
R"cc(
::memset(reinterpret_cast<char *>(&$impl$) +
offsetof($Impl$, $first$_),
0,
offsetof($Impl$, $last$_) -
offsetof($Impl$, $first$_) +
sizeof($Impl$::$last$_));
)cc");
} else {
p->Emit({{"field", FieldMemberName(first, false)}},
R"cc(
$field$ = {};
)cc");
}
first = nullptr;
}
};
auto it = optimized_order_.begin();
auto end = optimized_order_.end();
for (; it != end && !ShouldSplit(*it, options_); ++it) {
auto const& generator = field_generators_.get(*it);
if (generator.has_trivial_zero_default()) {
if (first == nullptr) first = *it;
} else {
emit_pending_zero_fields(it);
}
}
emit_pending_zero_fields(it);
}
namespace {
class MemberInitSeparator {
public:
explicit MemberInitSeparator(io::Printer* printer) : printer_(printer) {}
MemberInitSeparator(const MemberInitSeparator&) = delete;
~MemberInitSeparator() {
if (separators_) printer_->Outdent();
}
void operator()() {
if (separators_) {
printer_->Emit(",\n");
} else {
printer_->Emit(": ");
printer_->Indent();
separators_ = true;
}
}
private:
bool separators_ = false;
io::Printer* const printer_;
};
} // namespace
void MessageGenerator::GenerateImplMemberInit(io::Printer* p,
InitType init_type) {
ABSL_DCHECK(!HasSimpleBaseClass(descriptor_, options_));
auto indent = p->WithIndent();
MemberInitSeparator separator(p);
auto init_extensions = [&] {
if (descriptor_->extension_range_count() > 0 &&
init_type != InitType::kConstexpr) {
separator();
p->Emit("_extensions_{visibility, arena}");
}
};
auto init_inlined_string_indices = [&] {
if (!inlined_string_indices_.empty()) {
bool dtor_on_demand = NeedsArenaDestructor() == ArenaDtorNeeds::kOnDemand;
auto values = [&] {
for (size_t i = 0; i < InlinedStringDonatedSize(); ++i) {
p->Emit(i ? ", ~0u" : dtor_on_demand ? "~0u" : "0xFFFFFFFEu");
}
};
separator();
p->Emit({{"values", values}}, "_inlined_string_donated_{$values$}");
}
};
auto init_has_bits = [&] {
if (!has_bit_indices_.empty()) {
if (init_type == InitType::kArenaCopy) {
separator();
p->Emit("_has_bits_{from._has_bits_}");
}
separator();
p->Emit("_cached_size_{0}");
}
};
auto init_fields = [&] {
for (auto* field : optimized_order_) {
if (ShouldSplit(field, options_)) continue;
auto const& generator = field_generators_.get(field);
switch (init_type) {
case InitType::kConstexpr:
separator();
generator.GenerateMemberConstexprConstructor(p);
break;
case InitType::kArena:
if (!generator.has_trivial_zero_default()) {
separator();
generator.GenerateMemberConstructor(p);
}
break;
case InitType::kArenaCopy:
if (!generator.has_trivial_value()) {
separator();
generator.GenerateMemberCopyConstructor(p);
}
break;
}
}
};
auto init_split = [&] {
if (ShouldSplit(descriptor_, options_)) {
separator();
p->Emit({{"name", DefaultInstanceName(descriptor_, options_, true)}},
"_split_{const_cast<Split*>(&$name$._instance)}");
}
};
auto init_oneofs = [&] {
for (auto oneof : OneOfRange(descriptor_)) {
separator();
p->Emit({{"name", oneof->name()}}, "$name$_{}");
}
};
auto init_cached_size_if_no_hasbits = [&] {
if (has_bit_indices_.empty()) {
separator();
p->Emit("_cached_size_{0}");
}
};
auto init_oneof_cases = [&] {
if (int count = descriptor_->real_oneof_decl_count()) {
separator();
if (init_type == InitType::kArenaCopy) {
auto cases = [&] {
for (int i = 0; i < count; i++) {
p->Emit({{"index", i}, {"comma", i ? ", " : ""}},
"$comma$from._oneof_case_[$index$]");
}
};
p->Emit({{"cases", cases}}, "_oneof_case_{$cases$}");
} else {
p->Emit("_oneof_case_{}");
}
}
};
auto init_weak_field_map = [&] {
if (num_weak_fields_ && init_type != InitType::kConstexpr) {
separator();
if (init_type == InitType::kArenaCopy) {
p->Emit("_weak_field_map_{visibility, arena, from._weak_field_map_}");
} else {
p->Emit("_weak_field_map_{visibility, arena}");
}
}
};
auto init_any_metadata = [&] {
if (IsAnyMessage(descriptor_, options_)) {
separator();
p->Emit("_any_metadata_{&type_url_, &value_}");
}
};
// Initialization order of the various fields inside `_impl_(...)`
init_extensions();
init_inlined_string_indices();
init_has_bits();
init_fields();
init_split();
init_oneofs();
init_cached_size_if_no_hasbits();
init_oneof_cases();
init_weak_field_map();
init_any_metadata();
}
void MessageGenerator::GenerateSharedConstructorCode(io::Printer* p) {
if (HasSimpleBaseClass(descriptor_, options_)) return;
// Generate Impl_::Imp_(visibility, Arena*);
p->Emit({{"init_impl", [&] { GenerateImplMemberInit(p, InitType::kArena); }},
{"zero_init", [&] { GenerateZeroInitFields(p); }}},
R"cc(
inline PROTOBUF_NDEBUG_INLINE $classname$::Impl_::Impl_(
$pbi$::InternalVisibility visibility,
::$proto_ns$::Arena* arena)
//~
$init_impl$ {}
inline void $classname$::SharedCtor(::_pb::Arena* arena) {
new (&_impl_) Impl_(internal_visibility(), arena);
$zero_init$;
}
)cc");
}
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void MessageGenerator::GenerateInitDefaultSplitInstance(io::Printer* p) {
if (!ShouldSplit(descriptor_, options_)) return;
@ -2414,10 +2673,12 @@ void MessageGenerator::GenerateSharedDestructorCode(io::Printer* p) {
{
{"extensions_dtor",
[&] {
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
if (descriptor_->extension_range_count() == 0) return;
p->Emit(R"cc(
$extensions$.~ExtensionSet();
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}},
{"field_dtors", [&] { emit_field_dtors(/* split_fields= */ false); }},
{"split_field_dtors",
@ -2457,10 +2718,18 @@ void MessageGenerator::GenerateSharedDestructorCode(io::Printer* p) {
}},
{"any_metadata_dtor",
[&] {
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
if (!IsAnyMessage(descriptor_, options_)) return;
p->Emit(R"cc(
$any_metadata$.~AnyMetadata();
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}},
{"impl_dtor",
[&] {
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
p->Emit("_impl_.~Impl_();\n");
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
}},
},
R"cc(
@ -2472,6 +2741,7 @@ void MessageGenerator::GenerateSharedDestructorCode(io::Printer* p) {
$oneof_field_dtors$;
$weak_fields_dtor$;
$any_metadata_dtor$;
$impl_dtor$;
}
)cc");
}
@ -2553,6 +2823,9 @@ void MessageGenerator::GenerateConstexprConstructor(io::Printer* p) {
)cc");
return;
}
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
bool need_to_emit_cached_size = !HasSimpleBaseClass(descriptor_, options_);
p->Emit(
{
@ -2633,6 +2906,30 @@ void MessageGenerator::GenerateConstexprConstructor(io::Printer* p) {
$constexpr$ $classname$::$classname$(::_pbi::ConstantInitialized)
: _impl_{$init_body$} {}
)cc");
#else // !PROTOBUF_EXPLICIT_CONSTRUCTORS
// Generate Impl_::Imp_(::_pbi::ConstantInitialized);
// We use separate p->Emit() calls for LF and #ifdefs as they result in
// awkward layout and more awkward indenting of the function statement.
p->Emit("\n");
p->Emit({{"init", [&] { GenerateImplMemberInit(p, InitType::kConstexpr); }}},
R"cc(
inline constexpr $classname$::Impl_::Impl_(
::_pbi::ConstantInitialized) noexcept
//~
$init$ {}
)cc");
p->Emit("\n");
p->Emit(
R"cc(
template <typename>
$constexpr$ $classname$::$classname$(::_pbi::ConstantInitialized)
: _impl_(::_pbi::ConstantInitialized()) {}
)cc");
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
}
void MessageGenerator::GenerateCopyConstructorBody(io::Printer* p) const {
@ -2849,6 +3146,231 @@ void MessageGenerator::GenerateCopyConstructorBodyOneofs(io::Printer* p) const {
}
}
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
void MessageGenerator::GenerateCopyInitFields(io::Printer* p) const {
auto begin = optimized_order_.begin();
auto end = optimized_order_.end();
const FieldDescriptor* first = nullptr;
auto emit_pending_copy_fields = [&](decltype(end) itend, bool split) {
if (first != nullptr) {
const FieldDescriptor* last = itend[-1];
if (first != last) {
p->Emit({{"first", FieldName(first)},
{"last", FieldName(last)},
{"Impl", split ? "Impl_::Split" : "Impl_"},
{"pdst", split ? "_impl_._split_" : "&_impl_"},
{"psrc", split ? "from._impl_._split_" : "&from._impl_"}},
R"cc(
::memcpy(reinterpret_cast<char *>($pdst$) +
offsetof($Impl$, $first$_),
reinterpret_cast<const char *>($psrc$) +
offsetof($Impl$, $first$_),
offsetof($Impl$, $last$_) -
offsetof($Impl$, $first$_) +
sizeof($Impl$::$last$_));
)cc");
} else {
p->Emit({{"field", FieldMemberName(first, split)}},
R"cc(
$field$ = from.$field$;
)cc");
}
first = nullptr;
}
};
int has_bit_word_index = -1;
auto load_has_bits = [&](const FieldDescriptor* field) {
if (has_bit_indices_.empty()) return;
int has_bit_index = has_bit_indices_[field->index()];
if (has_bit_word_index != has_bit_index / 32) {
p->Emit({{"declare", has_bit_word_index < 0 ? "::uint32_t " : ""},
{"index", has_bit_index / 32}},
"$declare$cached_has_bits = _impl_._has_bits_[$index$];\n");
has_bit_word_index = has_bit_index / 32;
}
};
auto has_message = [&](const FieldDescriptor* field) {
if (has_bit_indices_.empty()) {
p->Emit("from.$field$ != nullptr");
} else {
int index = has_bit_indices_[field->index()];
std::string mask = absl::StrFormat("0x%08xu", 1u << (index % 32));
p->Emit({{"mask", mask}}, "cached_has_bits & $mask$");
}
};
auto emit_copy_message = [&](const FieldDescriptor* field) {
load_has_bits(field);
p->Emit({{"has_msg", [&] { has_message(field); }},
{"submsg", FieldMessageTypeName(field, options_)}},
R"cc(
$field$ = ($has_msg$)
? CreateMaybeMessage<$submsg$>(arena, *from.$field$)
: nullptr;
)cc");
};
auto generate_copy_fields = [&] {
for (auto it = begin; it != end; ++it) {
const auto& gen = field_generators_.get(*it);
auto v = p->WithVars(FieldVars(*it, options_));
// Non trivial field values are copy constructed
if (!gen.has_trivial_value() || gen.should_split()) {
emit_pending_copy_fields(it, false);
continue;
}
if (gen.is_message()) {
emit_pending_copy_fields(it, false);
emit_copy_message(*it);
} else if (first == nullptr) {
first = *it;
}
}
emit_pending_copy_fields(end, false);
};
auto generate_copy_split_fields = [&] {
for (auto it = begin; it != end; ++it) {
const auto& gen = field_generators_.get(*it);
auto v = p->WithVars(FieldVars(*it, options_));
if (!gen.should_split()) {
emit_pending_copy_fields(it, true);
continue;
}
if (gen.is_trivial()) {
if (first == nullptr) first = *it;
} else {
emit_pending_copy_fields(it, true);
gen.GenerateCopyConstructorCode(p);
}
}
emit_pending_copy_fields(end, true);
};
auto generate_copy_oneof_fields = [&]() {
for (const auto* oneof : OneOfRange(descriptor_)) {
p->Emit(
{{"name", oneof->name()},
{"NAME", absl::AsciiStrToUpper(oneof->name())},
{"cases",
[&] {
for (const auto* field : FieldRange(oneof)) {
p->Emit(
{{"Name", UnderscoresToCamelCase(field->name(), true)},
{"field", FieldMemberName(field, /*split=*/false)},
{"body",
[&] {
field_generators_.get(field).GenerateOneofCopyConstruct(
p);
}}},
R"cc(
case k$Name$:
$body$;
break;
)cc");
}
}}},
R"cc(
switch ($name$_case()) {
case $NAME$_NOT_SET:
break;
$cases$;
}
)cc");
}
};
if (descriptor_->extension_range_count() > 0) {
p->Emit(R"cc(
_impl_._extensions_.MergeFrom(this, from._impl_._extensions_);
)cc");
}
p->Emit({{"copy_fields", generate_copy_fields},
{"copy_oneof_fields", generate_copy_oneof_fields}},
R"cc(
$copy_fields$;
$copy_oneof_fields$;
)cc");
if (ShouldSplit(descriptor_, options_)) {
p->Emit({{"copy_split_fields", generate_copy_split_fields}},
R"cc(
if (PROTOBUF_PREDICT_FALSE(!from.IsSplitMessageDefault())) {
PrepareSplitMessageForWrite();
$copy_split_fields$;
}
)cc");
}
}
void MessageGenerator::GenerateArenaEnabledCopyConstructor(io::Printer* p) {
if (!HasSimpleBaseClass(descriptor_, options_)) {
// Generate Impl_::Imp_(visibility, Arena*, const& from);
p->Emit(
{{"init", [&] { GenerateImplMemberInit(p, InitType::kArenaCopy); }}},
R"cc(
inline PROTOBUF_NDEBUG_INLINE $classname$::Impl_::Impl_(
$pbi$::InternalVisibility visibility, ::$proto_ns$::Arena* arena,
const Impl_& from)
//~
$init$ {}
)cc");
p->Emit("\n");
}
auto copy_construct_impl = [&] {
if (!HasSimpleBaseClass(descriptor_, options_)) {
p->Emit(R"cc(
new (&_impl_) Impl_(internal_visibility(), arena, from._impl_);
)cc");
}
};
auto force_allocation = [&] {
if (ShouldForceAllocationOnConstruction(descriptor_, options_)) {
p->Emit(R"cc(
//~ force alignment
#ifdef PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION
$mutable_unknown_fields$;
#endif // PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION
)cc");
}
};
p->Emit({{"copy_construct_impl", copy_construct_impl},
{"copy_init_fields", [&] { GenerateCopyInitFields(p); }},
{"force_allocation", force_allocation}},
R"cc(
$classname$::$classname$(
//~ force alignment
::$proto_ns$::Arena* arena,
//~ force alignment
const $classname$& from)
: $superclass$(arena) {
$classname$* const _this = this;
(void)_this;
_internal_metadata_.MergeFrom<$unknown_fields_type$>(
from._internal_metadata_);
$copy_construct_impl$;
$copy_init_fields$;
$force_allocation$;
// @@protoc_insertion_point(copy_constructor:$full_name$)
}
)cc");
}
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
void MessageGenerator::GenerateStructors(io::Printer* p) {
p->Emit(
{
@ -2875,6 +3397,7 @@ void MessageGenerator::GenerateStructors(io::Printer* p) {
)cc");
// Generate the copy constructor.
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
if (UsingImplicitWeakFields(descriptor_->file(), options_)) {
// If we are in lite mode and using implicit weak fields, we generate a
// one-liner copy constructor that delegates to MergeFrom. This saves some
@ -2924,6 +3447,33 @@ void MessageGenerator::GenerateStructors(io::Printer* p) {
}
)cc");
}
#else // !PROTOBUF_EXPLICIT_CONSTRUCTORS
if (UsingImplicitWeakFields(descriptor_->file(), options_)) {
// If we are in lite mode and using implicit weak fields, we generate a
// one-liner copy constructor that delegates to MergeFrom. This saves some
// code size and also cuts down on the complexity of implicit weak fields.
// We might eventually want to do this for all lite protos.
p->Emit(R"cc(
$classname$::$classname$(
//~ Force alignment
::$proto_ns$::Arena* arena, const $classname$& from)
: $classname$(arena) {
MergeFrom(from);
}
)cc");
} else if (ImplHasCopyCtor()) {
p->Emit(R"cc(
$classname$::$classname$(
//~ Force alignment
::$proto_ns$::Arena* arena, const $classname$& from)
: $classname$(arena) {
MergeFrom(from);
}
)cc");
} else {
GenerateArenaEnabledCopyConstructor(p);
}
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
// Generate the shared constructor code.
GenerateSharedConstructorCode(p);
@ -2970,12 +3520,37 @@ void MessageGenerator::GenerateSourceInProto2Namespace(io::Printer* p) {
Formatter format(p);
if (ShouldGenerateExternSpecializations(options_) &&
ShouldGenerateClass(descriptor_, options_)) {
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
format(R"cc(
template <>
PROTOBUF_NOINLINE $classtype$* Arena::CreateMaybeMessage<$classtype$>(
Arena* arena) {
using T = $classtype$;
void* mem = arena != nullptr ? arena->AllocateAligned(sizeof(T))
: ::operator new(sizeof(T));
return new (mem) T(arena);
}
)cc");
if (!IsMapEntryMessage(descriptor_)) {
format(R"cc(
template <>
PROTOBUF_NOINLINE $classtype$* Arena::CreateMaybeMessage<$classtype$>(
Arena* arena, const $classtype$& from) {
using T = $classtype$;
void* mem = arena != nullptr ? arena->AllocateAligned(sizeof(T))
: ::operator new(sizeof(T));
return new (mem) T(arena, from);
}
)cc");
}
#else // PROTOBUF_EXPLICIT_CONSTRUCTORS
format(
"template<> "
"PROTOBUF_NOINLINE $classtype$*\n"
"Arena::CreateMaybeMessage< $classtype$ >(Arena* arena) {\n"
" return Arena::CreateMessageInternal< $classtype$ >(arena);\n"
"}\n");
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
}
}

@ -103,6 +103,8 @@ class MessageGenerator {
const Descriptor* descriptor() const { return descriptor_; }
private:
enum class InitType { kConstexpr, kArena, kArenaCopy };
// Generate declarations and definitions of accessors for fields.
void GenerateFieldAccessorDeclarations(io::Printer* p);
void GenerateFieldAccessorDefinitions(io::Printer* p);
@ -110,12 +112,22 @@ class MessageGenerator {
// Generate constructors and destructor.
void GenerateStructors(io::Printer* p);
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
void GenerateZeroInitFields(io::Printer* p) const;
void GenerateCopyInitFields(io::Printer* p) const;
void GenerateImplMemberInit(io::Printer* p, InitType init_type);
void GenerateArenaEnabledCopyConstructor(io::Printer* p);
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
// The compiler typically generates multiple copies of each constructor and
// destructor: http://gcc.gnu.org/bugs.html#nonbugs_cxx
// Placing common code in a separate method reduces the generated code size.
//
// Generate the shared constructor code.
void GenerateSharedConstructorCode(io::Printer* p);
// Generate the shared destructor code.
void GenerateSharedDestructorCode(io::Printer* p);
// Generate the arena-specific destructor code.

@ -472,6 +472,13 @@ class PROTOBUF_EXPORT MessageLite {
return Arena::CreateMaybeMessage<T>(arena);
}
#ifdef PROTOBUF_EXPLICIT_CONSTRUCTORS
template <typename T>
static T* CreateMaybeMessage(Arena* arena, const T& from) {
return Arena::CreateMaybeMessage<T>(arena, from);
}
#endif // PROTOBUF_EXPLICIT_CONSTRUCTORS
inline explicit MessageLite(Arena* arena) : _internal_metadata_(arena) {}
// Returns the arena, if any, that directly owns this message and its internal

Loading…
Cancel
Save