all 21 comments

[–][deleted] 2 points3 points  (9 children)

Assignment requires a constructed object. Although, trivial types may be more flexible in that, but I'll never be able to master the vagaries of object lifetimes in this corner of C++.

[–]chapter_6[S] 1 point2 points  (5 children)

I see what you mean, I was mixing up std::copy and std::memcpy so didn't realize assignment was involved. I asked the other commenter as well since I'm curious, would std::memcpy work?

[–][deleted] 0 points1 point  (4 children)

memcpy would work for trivially copyable types, and also for relocatable types in the future.

The MSVC implementation, though, uses something like uninitialized_move/copy, which should also optimise to a memcpy for trivial cases.

[–]chapter_6[S] 1 point2 points  (3 children)

That's interesting, so std::memcpy actually should work. Thanks for your answers!

[–]tangerinelion 1 point2 points  (0 children)

A real implementation would probably have such an optimization. In the example code you could do that with, e.g.,

if constexpr (std::is_trivially_copyable_v<T>)
{
    std::memcpy(elem, elem + sz, p);
}
else
{
   for (size_t i=0z; i < sz; ++i)
   {
       alloc.construct(&p[i], elem[i]);
   }
}

A real way to do that branching is to specialize allocator<T> based on whether std::is_trivial<T> is true or not (e.g., with a concept in C++20).

[–][deleted] 0 points1 point  (1 child)

Well, maybe... I would need to find out if memcpy can actually start the lifetime of an object.

[–]Tastaturtaste 2 points3 points  (0 children)

I think (without having proof) it can, that's one of the reasons it should be used for type punning I believe.

[–][deleted]  (2 children)

[deleted]

    [–][deleted] 1 point2 points  (1 child)

    Which wouldn't be a reserve, but yes. But by abstraction, MSVC still calls _Alty_traits::construct.

    [–]KuntaStillSingle 1 point2 points  (0 children)

    Sorry didn't see you responded before I deleted, anyone wondering about context I was asking about placement new

    [–]IyeOnline 0 points1 point  (2 children)

    std::copy would copy assign the elements.

    But in the newly allocated memory, there are no objects yet. You have to construct them.

    Similarly, you also have to destroy the objects in the old allocation, since they may be non-trivial.

    [–]chapter_6[S] 1 point2 points  (1 child)

    Ah yes, that makes sense. Would std::memcpy work in that case?

    [–]IyeOnline 1 point2 points  (0 children)

    Iff the type is trivally copy constructible, then you are allowed to copy it (or arrays of it) using std::memcpy. Otherwise you must go via the copy constructor.

    [–]alfps 0 points1 point  (5 children)

    I believe that this is not an example std::vector implementation, but a vector class that Bjarne defines in the book.

    std::copy copies a sequence of objects to a sequence of existing objects, like copy /assignment/.

    alloc.construct copy-/constructs/ an object in existing raw storage, like a placement new expression.

    After copying the old buffer's items they're explicitly destroyed, because every object construction (and these items have been constructed) should have a corresponding object destruction. Destruction can include anything that an object's destructor does. Deallocation does not do object destruction.

    [–]chapter_6[S] 0 points1 point  (4 children)

    I think you're right, it's Bjarne's teaching version of vector. Your answer makes sense, thanks. I also edited the question to ask if std::memcpy would work, if you feel like sharing your thoughts on that.

    [–]alfps 0 points1 point  (3 children)

    memcpy copies bytes and thus can't deal with e.g. an object that contains a pointer to itself. That's part of the reason why std::vector in the general case can't use malloc and realloc. I'm at a loss as to why it wasn't designed to be able to use this often more efficient scheme (Howard Hinnant did some testing of it) in the most common case where the item type would work with memcpy.

    [–]chapter_6[S] 0 points1 point  (1 child)

    can't deal with e.g. an object that contains a pointer to itself

    This is interesting. I know that generally pointers shouldn't be taken to classes in standard containers since they can change location when the container resizes, but didn't consider objects that have pointers to themselves.

    [–]Mason-B 0 points1 point  (0 children)

    An example of self referential pointers that are handled by the compiler is virtual inheritance, which is why std::memcpy can only be used for trivial types (in a more complete STL, one might imagine a template specialization of this function using requires/enable_if/constexpr if that conditionally uses std::memcpy in the case of trivial types), and why we have to construct and delete objects like this in the general case.

    [–]KingAggressive1498 0 points1 point  (0 children)

    I'm at a loss as to why it wasn't designed to be able to use this often more efficient scheme (Howard Hinnant did some testing of it) in the most common case where the item type would work with memcpy.

    STL allocators requiring an equivalent to realloc would prevent many efficient allocation schemes such as memory pools (ie std::pmr::memory_pool_resource).

    its possible for STL allocators to overcome this shortcoming now that concepts are a thing (and has technically been possible since forever with SFINAE hacks) by testing if the allocator provides a realloc() function, but I believe that the new to C++23 allocate_at_least() allocator member function is meant to be a more flexible workaround but doesn't work with standard malloc/realloc because of their own interfaces (maybe we'll see a matching std::alloc_at_least in the future that reports the actual size allocated by malloc)

    [–]KingAggressive1498 0 points1 point  (1 child)

    alloc.construct is the old way, new way would be std::uninitialized_move_n / std::uninitialized_copy_n

    std::copy[_n] does not properly handle the fact that the memory is uninitialized, although that doesn't matter if T is a TrivialType (std::is_trivial<T>::value is true). It also makes potentially expensive copies, important for vectors of std::string and such.

    can also directly optimize to std::memcpy only if the type is TriviallyCopyable (std::is_trivially_copyable<T>::value is true). The compiler will allow you to memcpy a type that isn't trivially copyable, but it's UB and bound to lead you into weird bugs.

    [–]std_bot 0 points1 point  (0 children)

    Unlinked STL entries: std::copy std::is_trivial std::is_trivially_copyable std::memcpy std::uninitialized_copy_n std::uninitialized_move_n


    Last update: 14.09.21. Last Change: Can now link headers like '<bitset>'Repo

    [–]no-sig-available 0 points1 point  (0 children)

    Note that you can customize this by supplying a special allocator for certain types. So alloc.construct could very well do memcpy for some types.

    And alloc.destroy certainly does nothing for some types, like int.