考虑这三个类:
struct Foo
{
// causes default ctor to be deleted
constexpr explicit Foo(int i) noexcept : _i(i) {}
private:
int _i;
};
// same as Foo but default ctor is brought back and explicitly defaulted
struct Bar
{
constexpr Bar() noexcept = default;
constexpr explicit Bar(int i) noexcept : _i(i) {}
private:
int _i;
};
// same as Bar but member variable uses brace-or-equals initializer (braces in this case)
struct Baz
{
constexpr Baz() noexcept = default;
constexpr explicit Baz(int i) noexcept : _i(i) {}
private:
int _i{};
};
Run Code Online (Sandbox Code Playgroud)
以下static_asserts 的计算结果为 true (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>);
Run Code Online (Sandbox Code Playgroud)
这意味着只有 Bar 被认为是普通默认可构造的。
我理解如何Foo并且Baz不满足标准定义的条件,但我不明白的是为什么这意味着某些算法可以以Bar它们无法做到的方式优化操作Foo或Baz。
运行时测试示例展示了普通默认可构造的好处: https://quick-bench.com/q/t1W4ItmCoJ60U88_ED9s_7I9Cl0
该测试用 1000 个随机生成的对象填充向量,并测量这样做的运行时间。与int,,,一起跑。我的猜测是矢量重新分配和对象的复制/移动是性能差异体现出来的地方。FooBarBaz
什么是普通默认可构造以实现优化?
为什么编译器(或 std::vector 实现)无法对Foo和应用相同的优化Baz?
这是 gcc 错过的优化。
基本上,问题是:当vector必须重新分配时,如何将元素从旧存储转移到新存储?gcc 的实现当前尝试执行此操作(为了简洁起见,我删除了一些不相关的代码块):
// This class may be specialized for specific types.
// Also known as is_trivially_relocatable.
template<typename _Tp, typename = void>
struct __is_bitwise_relocatable
: is_trivial<_Tp> { };
template <typename _InputIterator, typename _ForwardIterator,
typename _Allocator>
_GLIBCXX20_CONSTEXPR
inline _ForwardIterator
__relocate_a_1(_InputIterator __first, _InputIterator __last,
_ForwardIterator __result, _Allocator& __alloc)
noexcept(noexcept(std::__relocate_object_a(std::addressof(*__result),
std::addressof(*__first),
__alloc)))
{
_ForwardIterator __cur = __result;
for (; __first != __last; ++__first, (void)++__cur)
std::__relocate_object_a(std::__addressof(*__cur),
std::__addressof(*__first), __alloc);
return __cur;
}
template <typename _Tp, typename _Up>
_GLIBCXX20_CONSTEXPR
inline __enable_if_t<std::__is_bitwise_relocatable<_Tp>::value, _Tp*>
__relocate_a_1(_Tp* __first, _Tp* __last,
_Tp* __result,
[[__maybe_unused__]] allocator<_Up>& __alloc) noexcept
{
ptrdiff_t __count = __last - __first;
if (__count > 0)
{
__builtin_memmove(__result, __first, __count * sizeof(_Tp));
}
return __result + __count;
}
Run Code Online (Sandbox Code Playgroud)
这里的第一个重载执行按成员复制,第二个重载执行单个复制memmove- 但前提是类型满足__is_bitwise_relocatable<_Tp>,如您所见,默认为std::is_trivial。std::is_trivial 需要一个简单的默认构造函数,这实际上与此特定优化无关(请参阅#68350,但这就是导致代码路径执行缓慢的逐元素复制而不是单个 memmove 的原因。
您可以通过专门化并查看现在的__is_bitwise_relocatable<Foo>性能来验证情况是否如此。