平凡默认可构造类型性能优势解析:Foo/Baz为何无法获同等优化?
平凡默认可构造类型的性能差异解析
首先看三个C++结构体定义:
struct Foo { // 导致默认构造函数被删除 constexpr explicit Foo(int i) noexcept : _i(i) {} private: int _i; }; // 与Foo相同,但恢复并显式默认默认构造函数 struct Bar { constexpr Bar() noexcept = default; constexpr explicit Bar(int i) noexcept : _i(i) {} private: int _i; }; // 与Bar相同,但成员变量使用大括号或等号初始化器(此处为大括号) struct Baz { constexpr Baz() noexcept = default; constexpr explicit Baz(int i) noexcept : _i(i) {} private: int _i{}; };
以下断言在C++20中均成立:
static_assert(not std::is_trivially_default_constructible_v<Foo>); static_assert(std::is_trivially_default_constructible_v<Bar>); static_assert(not std::is_trivially_default_constructible_v<Baz>);
可见仅Bar属于平凡默认可构造类型,下面针对两个核心问题解答:
1. 平凡默认可构造的特性为何能支持性能优化?
平凡默认可构造的核心是类型的默认构造行为完全由编译器生成,无任何用户自定义初始化逻辑,且所有成员都是平凡构造的。这给编译器和标准库带来两个关键优化空间:
- 跳过构造函数调用:比如
std::vector扩容时,不需要循环调用默认构造函数初始化每个元素,直接用底层内存分配(如malloc)开辟整块内存即可,省去大量函数调用开销。 - 内存级批量操作替代对象操作:可以用
memcpy、memset这类高效内存操作函数批量处理对象,而非执行对象层面的复制/构造逻辑,大幅提升批量操作的速度。 - 利用未初始化内存:对于内置类型成员,平凡构造不会强制初始化,标准库可以直接在后续步骤中覆盖赋值,避免不必要的零初始化开销。
2. 编译器或std::vector实现为何无法对Foo和Baz应用相同的优化?
针对Foo:
Foo因用户自定义了带参构造函数,编译器自动删除了默认构造函数。std::vector扩容时,无法通过默认构造创建新内存区域的元素,只能将原有元素逐一复制/移动到新内存。这种逐个对象的操作开销远高于直接分配内存,尤其在元素数量较多时,性能差距会被放大。
针对Baz:
Baz虽然显式默认了默认构造函数,但成员_i使用了_i{}初始化器,这会让编译器生成的默认构造函数不再是平凡的——它必须执行对_i的零初始化逻辑。此时编译器无法跳过构造步骤,必须为每个Baz对象执行初始化操作,也就无法使用内存批量分配的优化,只能逐个构造元素,带来额外的初始化开销。
内容的提问来源于stack exchange,提问作者supernun
相关产品推荐
相关产品推荐

