uninitialized_copy
有两个职责:首先,它必须确保正确的位模式进入目标缓冲区。其次,它必须开始生命周期该缓冲区中的 C++ 对象的数量。也就是说,它必须调用某种构造函数,除非 C++ 标准明确授予它跳过该构造函数调用的权限。
根据我非常不完整的研究,目前看来只有可以简单地复制 https://en.cppreference.com/w/cpp/types/is_trivially_copyable类型保证其位模式被保留memcpy
/memmove
; memcpping 任何其他类型的类型(即使它碰巧是普通复制构造和/或普通复制分配!)会正式产生未定义的行为。
而且,现在看来只有trivial https://en.cppreference.com/w/cpp/types/is_trivial类型可以在没有构造函数调用的情况下“突然出现”。 (P0593“隐式创建对象...” http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0593r5.html提出了这方面的很多改变,也许是在 C++2b 中。)
乔纳森·韦克利的评论libstdc++ 错误 68350 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68350似乎表明 GNU libstdc++ 试图遵守法律规定,永远不会“突然出现”任何非平凡类型的对象 - 尽管作为 C++ 实现,它们确实有自由度来利用特定于平台的行为表演的名称。我猜测 MSVC 出于类似的原因(无论这些原因是什么)也遵循类似的逻辑。
通过比较供应商的优化意愿,您可以看到供应商不愿意“将对象弹出存在”std::copy
versus std::uninitialized_copy
类类型“可以简单复制,但并不简单”。可简单复制意味着std::copy
可以使用memcpy
分配现有对象;但std::uninitialized_copy
,为了让这些对象首先出现,仍然需要调用some循环中的构造函数 - 即使它是简单的复制构造函数!
class C { int i; public: C() = default; };
class D { int i; public: D() {} };
static_assert(std::is_trivially_copyable_v<C> && !std::is_aggregate_v<C>);
static_assert(std::is_trivially_copyable_v<D> && !std::is_aggregate_v<D>);
void copyCs(C *p, C *q, int n) {
std::copy(p, p+n, q); // GNU and MSVC both optimize
std::uninitialized_copy(p, p+n, q); // GNU and MSVC both optimize
}
void copyDs(D *p, D *q, int n) {
std::copy(p, p+n, q); // GNU and MSVC both optimize
std::uninitialized_copy(p, p+n, q); // neither GNU nor MSVC optimizes :(
}
你写了:
检查类型 U 是否可以从类型 T 简单地复制构造难道还不够吗?因为这就是 uninitialized_copy 所做的一切。
是的,但是当 T 和 U 是不同的,你不是在做“琐碎的复制构造”;你正在做一个“琐碎的构造”not复制构造。不幸的是 C++ 标准定义了is_trivially_constructible<T,U>
意思是与人类所说的“琐碎”不同的东西!我的博文“Trivially-constructible-from”(2018 年 7 月) https://quuxplusone.github.io/blog/2018/07/03/trivially-constructible-from/给出这个例子:
assert(is_trivially_constructible_v<u64, u64b>);
// Yay!
using u16 = short;
assert(is_trivially_constructible_v<u64, u16>);
// What the...
assert(is_trivially_constructible_v<u64, double>);
// ...oh geez.
这解释了 MSVC 的一些
额外检查(如 sizeof(T) == sizeof(U))如果 T != U
具体来说,MSVC_Ptr_cat_helper<T*,U*>::_Really_trivial https://github.com/microsoft/STL/blob/4aaa0135d965e41690bbd555c71d74e34d459c26/stl/inc/xutility#L1382-L1417Trait 依赖于这些额外的检查来检测一些(但不是全部)常见情况,其中从 T 到 U 的转换在人类/按位意义上“确实”微不足道,而不仅仅是在 C++ 标准意义上微不足道。这允许 MSVC 优化复制数组int*
到一个数组中const int*
,这是 libstdc++ 无法做到的:
using A = int*;
using B = const int*;
void copyAs(A *p, B *q, int n) {
std::uninitialized_copy(p, p+n, q); // only MSVC optimizes
}
void copyBs(B *p, B *q, int n) {
std::uninitialized_copy(p, p+n, q); // GNU and MSVC both optimize
}