I've recently started to examine the STL in the MSVC's implementation. There are some nice tricks there, however I don't know why the following criteria is used.
The std::uninitialized_copy
is optimized to a simple memcpy/memmove
if some conditions are met. As my understanding the input range can be memcpy
'd to the uninitialized area if the target type U is_trivially_copy_constructible
from source type T.
However the MSVC implementation checks a hell lot of thing before choosing the memcpy
instead of the one-by-one copy-constructing of elements. I did not want to paste the related code here, instead I'm sharing it through pastebin if anyone is interested: https://pastebin.com/Sa4Q7Qj0
The base algorithm for the uninitialized_copy
is something like this (exception-handling is omitted for readibility)
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);
}
This can be optimized to a memcpy/memmove
if the copy-constructing doesn't do any 'special' thing (trivially copy-constructible).
The MS's implementation requires the following:
- T trivially assignable to U
- T trivially copyable to U
- T being trivial
- extra checks (like sizeof(T) == sizeof(U)) if T != U
So for example the following struct cannot be memcpy
'd:
struct Foo
{
int i;
Foo() : i(10) { }
};
but the following is ok:
struct Foo
{
int i;
Foo() = default; // or simply omit
};
Shouldn't it be enough to check if type U can be trivially copy-constructed from type T? Because all that's the uninitialized_copy does.
For example, I can't see why the following is not memcpy'd by the MS's STL implementation (NOTE: I know the reason, it is the user-defined constructor, but I don't understand the logic behind it):
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);
}
Related SO post: Why doesn't gcc use memmove in std::uninitialized_copy?
Update
As @Igor Tandetnik pointed out in the comments, it is not safe to say if there is no user-defined copy constructor then the type T is trivially copy-constructible. He provided the following example:
struct Foo
{
std::string data;
};
In this example, there is no user-defined copy constructor and it is still not trivially copy-constructible. Thank you for the correction, I modified the original post based on the feedback.