我最近开始研究MSVC实现中的STL。那里有一些不错的技巧,但是我不知道为什么使用以下标准。
如果满足某些条件,std::uninitialized_copy
将优化为简单的memcpy/memmove
。据我了解,如果源类型T的目标类型U memcpy
,则输入范围可以is_trivially_copy_constructible
到未初始化的区域。
但是,MSVC实现在选择memcpy
之前要检查很多事情,而不是元素的一对一复制构造。我不想在此处粘贴相关代码,如果有人感兴趣,我将通过pastebin共享它:https://pastebin.com/Sa4Q7Qj0
uninitialized_copy
的基本算法是这样的(为便于阅读,省略了异常处理)
template <typename T, typename... Args> inline void construct_in_place(T& obj, Args&&... args) { ::new (static_cast<void*>(addressof(obj)) T(forward<Args>(args)...); } template <typename In, typename Out> inline Out uninitialized_copy(In first, In last, Out dest) { for (; first != last; ++first, ++dest) construct_in_place(*dest, *first); }
如果复制构造不执行任何“特殊”操作(通常是可复制构造的,则可以将其优化为
memcpy/memmove
。
MS的实现需要以下条件:
例如,以下结构不能memcpy
:
struct Foo { int i; Foo() : i(10) { } };
但以下内容可以:
struct Foo { int i; Foo() = default; // or simply omit };
仅检查类型U是否可以从类型T中复制构造就足够了吗?因为所有这些都是uninitialized_copy所做的。
例如,我看不到为什么MS的STL实现不对以下内容进行注释(注意:我知道原因,它是用户定义的构造函数,但我不理解其背后的逻辑) :
struct Foo { int i; Foo() noexcept : i(10) { } Foo(const Foo&) = default; }; void test() { // please forgive me... uint8 raw[256]; Foo* dest = (Foo*)raw; Foo src[] = { Foo(), Foo() }; bool b = std::is_trivially_copy_constructible<Foo>::value; // true bool b2 = std::is_trivially_copyable<Foo>::value; // true memcpy(dest, src, sizeof(src)); // seems ok // uninitialized_copy does not use memcpy/memmove, it calls the copy-ctor one-by-one std::uninitialized_copy(src, src + sizeof(src) / sizeof(src[0]), dest); }
相关SO帖子:Why doesn't gcc use memmove in std::uninitialized_copy?
更新
正如@Igor Tandetnik在评论中指出的那样,如果没有用户定义的副本构造函数,则类型T几乎是可复制构造的,这是不安全的。他提供了以下示例:
struct Foo { std::string data; };
在此示例中,没有用户定义的副本构造函数,并且仍然不能轻易实现副本构造。谢谢您的纠正,我根据反馈修改了原始帖子。
我最近开始研究MSVC实现中的STL。那里有一些不错的技巧,但是我不知道为什么使用以下标准。 std :: uninitialized_copy是...
uninitialized_copy
有两个职责:首先,必须确保正确的位模式进入目标缓冲区。其次,它必须根据我非常不完整的研究,看来现在只有trivially copyable类型可以保证由memcpy
/ memmove
保留其位模式;记忆任何其他类型的类型(即使它恰好是普通可复制构造的和/或普通可复制赋值的!)也会产生不确定的行为。