[C++] Support C++ object copies and moves (#5988)

Augment the C++ generator to emit a C++ copy constructor and a by-value
copy assignment operator. This is enabled by default when the C++
standard is C++11 or later. These additional functions are only emitted
for objects that need it, typically tables containing other tables.

These new functions are declared in the object table type and are
defined as inline functions after table declarations.

When these new functions are declared, a user-defined
explicitly-defaulted default constructor and move constructor are also
emitted.

The copy assignment operator uses the copy-and-swap idiom to provide
strong exception safety, at the expense of keeping 2 full table copies
in memory temporarily.

fixes #5783
This commit is contained in:
Jean-François Roy
2022-01-29 14:24:24 -08:00
committed by GitHub
parent 5993338ee3
commit 43203984f7
7 changed files with 547 additions and 59 deletions

View File

@@ -810,7 +810,7 @@ class CppGenerator : public BaseGenerator {
}
std::string GenTypeNative(const Type &type, bool invector,
const FieldDef &field) {
const FieldDef &field, bool forcopy = false) {
switch (type.base_type) {
case BASE_TYPE_STRING: {
return NativeString(&field);
@@ -831,15 +831,14 @@ class CppGenerator : public BaseGenerator {
if (IsStruct(type)) {
auto native_type = type.struct_def->attributes.Lookup("native_type");
if (native_type) { type_name = native_type->constant; }
if (invector || field.native_inline) {
if (invector || field.native_inline || forcopy) {
return type_name;
} else {
return GenTypeNativePtr(type_name, &field, false);
}
} else {
return GenTypeNativePtr(
WrapNativeNameInNameSpace(*type.struct_def, opts_), &field,
false);
const auto nn = WrapNativeNameInNameSpace(*type.struct_def, opts_);
return forcopy ? nn : GenTypeNativePtr(nn, &field, false);
}
}
case BASE_TYPE_UNION: {
@@ -1601,7 +1600,8 @@ class CppGenerator : public BaseGenerator {
code_.SetValue("TYPE", GetUnionElement(ev, true, opts_));
code_ += " case {{LABEL}}: {";
bool copyable = true;
if (ev.union_type.base_type == BASE_TYPE_STRUCT &&
if (opts_.g_cpp_std < cpp::CPP_STD_11 &&
ev.union_type.base_type == BASE_TYPE_STRUCT &&
!ev.union_type.struct_def->fixed) {
// Don't generate code to copy if table is not copyable.
// TODO(wvo): make tables copyable instead.
@@ -1804,13 +1804,50 @@ class CppGenerator : public BaseGenerator {
}
}
// Returns true if `struct_def` needs a copy constructor and assignment
// operator because it has one or more table members, struct members with a
// custom cpp_type and non-naked pointer type, or vector members of those.
bool NeedsCopyCtorAssignOp(const StructDef &struct_def) {
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
const auto &field = **it;
const auto &type = field.value.type;
if (field.deprecated) continue;
if (type.base_type == BASE_TYPE_STRUCT) {
const auto cpp_type = field.attributes.Lookup("cpp_type");
const auto cpp_ptr_type = field.attributes.Lookup("cpp_ptr_type");
const bool is_ptr = !(IsStruct(type) && field.native_inline) ||
(cpp_type && cpp_ptr_type->constant != "naked");
if (is_ptr) { return true; }
} else if (IsVector(type)) {
const auto vec_type = type.VectorType();
if (vec_type.base_type == BASE_TYPE_UTYPE) continue;
const auto cpp_type = field.attributes.Lookup("cpp_type");
const auto cpp_ptr_type = field.attributes.Lookup("cpp_ptr_type");
const bool is_ptr =
(vec_type.base_type == BASE_TYPE_STRUCT && !IsStruct(vec_type)) ||
(cpp_type && cpp_ptr_type->constant != "naked");
if (is_ptr) { return true; }
}
}
return false;
}
// Generate the default constructor for this struct. Properly initialize all
// scalar members with default values.
void GenDefaultConstructor(const StructDef &struct_def) {
code_.SetValue("NATIVE_NAME",
NativeName(Name(struct_def), &struct_def, opts_));
// In >= C++11, default member initializers are generated.
if (opts_.g_cpp_std >= cpp::CPP_STD_11) { return; }
// In >= C++11, default member initializers are generated. To allow for
// aggregate initialization, do not emit a default constructor at all, with
// the exception of types that need a copy/move ctors and assignment
// operators.
if (opts_.g_cpp_std >= cpp::CPP_STD_11) {
if (NeedsCopyCtorAssignOp(struct_def)) {
code_ += " {{NATIVE_NAME}}() = default;";
}
return;
}
std::string initializer_list;
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
@@ -1854,6 +1891,125 @@ class CppGenerator : public BaseGenerator {
code_ += " }";
}
// Generate the >= C++11 copy/move constructor and assignment operator
// declarations if required. Tables that are default-copyable do not get
// user-provided copy/move constructors and assignment operators so they
// remain aggregates.
void GenCopyMoveCtorAndAssigOpDecls(const StructDef &struct_def) {
if (opts_.g_cpp_std < cpp::CPP_STD_11) return;
if (!NeedsCopyCtorAssignOp(struct_def)) return;
code_.SetValue("NATIVE_NAME",
NativeName(Name(struct_def), &struct_def, opts_));
code_ += " {{NATIVE_NAME}}(const {{NATIVE_NAME}} &o);";
code_ +=
" {{NATIVE_NAME}}({{NATIVE_NAME}}&&) FLATBUFFERS_NOEXCEPT = "
"default;";
code_ +=
" {{NATIVE_NAME}} &operator=({{NATIVE_NAME}} o) FLATBUFFERS_NOEXCEPT;";
}
// Generate the >= C++11 copy constructor and assignment operator definitions.
void GenCopyCtorAssignOpDefs(const StructDef &struct_def) {
if (opts_.g_cpp_std < cpp::CPP_STD_11) return;
if (!NeedsCopyCtorAssignOp(struct_def)) return;
std::string initializer_list;
std::string vector_copies;
std::string swaps;
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
const auto &field = **it;
const auto &type = field.value.type;
if (field.deprecated || type.base_type == BASE_TYPE_UTYPE) continue;
if (type.base_type == BASE_TYPE_STRUCT) {
if (!initializer_list.empty()) { initializer_list += ",\n "; }
const auto cpp_type = field.attributes.Lookup("cpp_type");
const auto cpp_ptr_type = field.attributes.Lookup("cpp_ptr_type");
auto type_name = (cpp_type) ? cpp_type->constant
: GenTypeNative(type, /*invector*/ false,
field, /*forcopy*/ true);
const bool is_ptr = !(IsStruct(type) && field.native_inline) ||
(cpp_type && cpp_ptr_type->constant != "naked");
CodeWriter cw;
cw.SetValue("FIELD", Name(field));
cw.SetValue("TYPE", type_name);
if (is_ptr) {
cw +=
"{{FIELD}}((o.{{FIELD}}) ? new {{TYPE}}(*o.{{FIELD}}) : "
"nullptr)\\";
initializer_list += cw.ToString();
} else {
cw += "{{FIELD}}(o.{{FIELD}})\\";
initializer_list += cw.ToString();
}
} else if (IsVector(type)) {
const auto vec_type = type.VectorType();
if (vec_type.base_type == BASE_TYPE_UTYPE) continue;
const auto cpp_type = field.attributes.Lookup("cpp_type");
const auto cpp_ptr_type = field.attributes.Lookup("cpp_ptr_type");
const auto type_name = (cpp_type)
? cpp_type->constant
: GenTypeNative(vec_type, /*invector*/ true,
field, /*forcopy*/ true);
const bool is_ptr =
(vec_type.base_type == BASE_TYPE_STRUCT && !IsStruct(vec_type)) ||
(cpp_type && cpp_ptr_type->constant != "naked");
CodeWriter cw(" ");
cw.SetValue("FIELD", Name(field));
cw.SetValue("TYPE", type_name);
if (is_ptr) {
// Use emplace_back to construct the potentially-smart pointer element
// from a raw pointer to a new-allocated copy.
cw.IncrementIdentLevel();
cw += "{{FIELD}}.reserve(o.{{FIELD}}.size());";
cw +=
"for (const auto &v : o.{{FIELD}}) { "
"{{FIELD}}.emplace_back((v) ? new {{TYPE}}(*v) : nullptr); }";
vector_copies += cw.ToString();
} else {
// For non-pointer elements, use std::vector's copy constructor in the
// initializer list. This will yield better performance than an insert
// range loop for trivially-copyable element types.
if (!initializer_list.empty()) { initializer_list += ",\n "; }
cw += "{{FIELD}}(o.{{FIELD}})\\";
initializer_list += cw.ToString();
}
} else {
if (!initializer_list.empty()) { initializer_list += ",\n "; }
CodeWriter cw;
cw.SetValue("FIELD", Name(field));
cw += "{{FIELD}}(o.{{FIELD}})\\";
initializer_list += cw.ToString();
}
{
if (!swaps.empty()) { swaps += "\n "; }
CodeWriter cw;
cw.SetValue("FIELD", Name(field));
cw += "std::swap({{FIELD}}, o.{{FIELD}});\\";
swaps += cw.ToString();
}
}
if (!initializer_list.empty()) {
initializer_list = "\n : " + initializer_list;
}
if (!swaps.empty()) { swaps = " " + swaps; }
code_.SetValue("NATIVE_NAME",
NativeName(Name(struct_def), &struct_def, opts_));
code_.SetValue("INIT_LIST", initializer_list);
code_.SetValue("VEC_COPY", vector_copies);
code_.SetValue("SWAPS", swaps);
code_ +=
"inline {{NATIVE_NAME}}::{{NATIVE_NAME}}(const {{NATIVE_NAME}} &o)"
"{{INIT_LIST}} {";
code_ += "{{VEC_COPY}}}\n";
code_ +=
"inline {{NATIVE_NAME}} &{{NATIVE_NAME}}::operator="
"({{NATIVE_NAME}} o) FLATBUFFERS_NOEXCEPT {";
code_ += "{{SWAPS}}";
code_ += " return *this;\n}\n";
}
void GenCompareOperator(const StructDef &struct_def,
std::string accessSuffix = "") {
std::string compare_op;
@@ -1942,6 +2098,7 @@ class CppGenerator : public BaseGenerator {
}
GenOperatorNewDelete(struct_def);
GenDefaultConstructor(struct_def);
GenCopyMoveCtorAndAssigOpDecls(struct_def);
code_ += "};";
code_ += "";
}
@@ -3120,6 +3277,9 @@ class CppGenerator : public BaseGenerator {
NativeName(Name(struct_def), &struct_def, opts_));
if (opts_.generate_object_based_api) {
// Generate the >= C++11 copy ctor and assignment operator definitions.
GenCopyCtorAssignOpDefs(struct_def);
// Generate the X::UnPack() method.
code_ +=
"inline " + TableUnPackSignature(struct_def, false, opts_) + " {";