Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Construction and initialization of trivial types in C++

A trivial class is trivially copyable and has a trivial default constructor (a trivial type is either one of those or a built-in class that works similarly).

Since you can use memcpy to copy objects of trivial types, and since default-initializing trivial types doesn't change any of the bytes in the representation¹, does the following code (using C++20 concepts) correctly initialize a copy of the passed-in object?

#include <cstdlib>
#include <cstring>

#include <new>
#include <type_traits>

template <typename T>
T* copy_trivial(T orig) requires std::is_trivial_v<T>
{
    void* buf = std::aligned_alloc(alignof(T), sizeof(T));

    // Note the order of these statements
    std::memcpy(buf, &orig, sizeof(T));
    return new(buf) T;
}

(Compiler Explorer, with a couple instantiations provided)

It seems like this code would work, because there would be an initialized object with the correct object representation. However, given that nothing happens after initialization, would the object instead have an indeterminate value²?


¹ Not specified in a single location, but the linked procedure for default-initialization calls the constructor, which must be implicitly defined; the implicitly defined constructor default-initializes all the members and bases; and that recursion bottoms out at no initialization is performed for the built-in trivial types.

² The note in [expr.new]p18.1 says that it would, but notes are non-normative and it's plausible that this is only the case for non-placement new.

like image 372
Daniel H Avatar asked Jul 15 '18 02:07

Daniel H


1 Answers

does the following code (using C++20 concepts) correctly initialize a copy of the passed-in object?

No, it does not.

It does return a pointer to a valid T, but there is nothing in the standard which requires the value of that T to be a copy of the value of orig.

Default initialization of trivial types is said to perform "no initialization". But this is not the same as preserving the current storage of that memory: [dcl.init]/12

When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced.

Note that it says that it retains "an indeterminate value", not "the same value that was in that memory". Without having that explicit protection, the standard does not require the implementation to preserve the memory's contents.

Consider debugging builds. To catch errors, cases where "no initialization is performed" will sometimes fill uninitialized memory with specific bytes, so that you can detect when you're accessing uninitialized memory. That is only legal for an implementation if "an indeterminate value" does not preserve the current values in the memory.

There is no way in C++ to initialize an object with a byte-wise copy of another object. You can do memcpy's into arbitrary memory, but you cannot manifest an object in that memory which takes its value from that memory. And you can memcpy into an existing object, but that object will already have been initialized (unless no initialization was performed when it was being created).

So the best you can do is reverse the order of the two statements.

like image 62
Nicol Bolas Avatar answered Oct 16 '22 23:10

Nicol Bolas