【发布时间】:2015-11-02 16:42:33
【问题描述】:
std::uninitialized_copy 复制到未初始化的内存范围。
这可以使用 memmove 按位可复制类型来完成。
我在 gdb 中逐步完成了下面的示例代码(使用 gcc 5.2.0 编译)。
因此我观察到memmove 根本没有被使用。
在示例中__is_trivial(Bar) 用于确定是否可以使用memmove。
它(正确地)计算为false,因为Bar 有一个重要的默认构造函数(参见bits/stl_uninitialized.h 行123ff 中对std::__uninitialized_copy<false>::__uninit_copy(...) 的调用)。
但是为什么__is_trivial 甚至与std::uninitialized_copy 相关?
根据Bjarne std::is_trivially_copyable 应该足够了。请注意,后者在示例中计算为true,即。 memmove优化is applicable。
我知道,该标准不需要std::uninitialized_copy 的任何特定实现。我只是想知道为什么 __is_trivial 受到青睐,即使 std::is_trivially_copyable 作为 gcc 实现的适用替代方案存在?
示例代码:
#include <iostream>
#include <memory>
#include <vector>
#include <type_traits>
struct Bar
{
Bar () : v(42) {};
Bar(Bar const &) = default;
Bar(Bar &&) = default;
Bar & operator=(Bar &&) = default;
Bar & operator=(Bar const &) = default;
~Bar() = default;
int v;
};
int main() {
std::cout
<< std::is_trivially_move_constructible<Bar>::value
<< " " << std::is_trivially_copy_constructible<Bar>::value
<< " " << std::is_trivially_copyable<Bar>::value
<< " " << std::is_trivial<Bar>::value
<< " " << __is_trivial(Bar) << std::endl;
size_t const num_elements = 1 << 27;
std::vector<Bar> v(num_elements);
Bar * vc = (Bar *) std::malloc(num_elements * sizeof(Bar));
std::uninitialized_copy(v.begin(), v.end(), vc);
std::free(vc);
}
示例输出:1 1 1 0 0
更新:我们做了一些测试,比较了memmove、uninitialized_copy 和一个简单的for 循环的实际运行时间。如果Bar 微不足道(参见__is_trivial(Bar)),uninitialized_copy 与memmove 一样快,否则,uninitialized_copy 与我们的for 循环一样快。总体而言,memmove 仅在较小的Bars 上明显更快(2x)(即将int v; 更改为char v;)。否则性能基本相同。
编辑:更正对std::is_trivially_... 的引用。更准确地说明标题。
【问题讨论】:
-
您混淆了
is_trivially_copy_constructible和is_trivially_copyable。 Bjarne 的示例代码使用的是第二个,而不是第一个。 -
没错,我已经更正了这个问题。问题仍然存在,因为
is_trivially_copyable也评估为true。 -
您复制到未初始化的内存范围。我解释它的方式,不能有重叠(因为一个被初始化另一个没有),所以你可以使用
memcpy而不是memmove。 -
libstdc++ 欢迎补丁(如果你不想花时间的话,也可以加入 bugzilla 条目)。
-
请注意,GCC 在 GCC 5 之前没有
__is_trivially_copyable内在函数。