Let's say you have an object of type T
and a suitably-aligned memory buffer alignas(T) unsigned char[sizeof(T)]
. If you use std::memcpy
to copy from the object of type T
to the unsigned char
array, is that considered copy construction or copy-assignment?
If a type is trivially-copyable but not standard-layout, it is conceivable that a class such as this:
struct Meow
{
int x;
protected: // different access-specifier means not standard-layout
int y;
};
could be implemented like this, because the compiler isn't forced into using standard-layout:
struct Meow_internal
{
private:
ptrdiff_t x_offset;
ptrdiff_t y_offset;
unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT];
};
The compiler could store x
and y
of Meow within buffer at any portion of buffer
, possibly even at a random offset within buffer
, so long as they are aligned properly and do not overlap. The offset of x
and y
could even vary randomly with each construction if the compiler wishes. (x
could go after y
if the compiler wishes because the Standard only requires members of the same access-specifier to go in order, and x
and y
have different access-specifiers.)
This would meet the requirements of being trivially-copyable; a memcpy
would copy the hidden offset fields, so the new copy would work. But some things would not work. For example, holding a pointer to x
across a memcpy
would break:
Meow a;
a.x = 2;
a.y = 4;
int *px = &a.x;
Meow b;
b.x = 3;
b.y = 9;
std::memcpy(&a, &b, sizeof(a));
++*px; // kaboom
However, is the compiler really allowed to implement a trivially-copyable class in this manner? Dereferencing px
should only be undefined behavior if a.x
's lifetime has ended. Has it? The relevant portions of the N3797 draft Standard aren't very clear on the subject. This is section [basic.life]/1:
The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [ Note: initialization by a trivial copy/move constructor is non-trivial initialization. — end note ] The lifetime of an object of type
T
begins when:
- storage with the proper alignment and size for type
T
is obtained, and- if the object has non-trivial initialization, its initialization is complete.
The lifetime of an object of type
T
ends when:
- if
T
is a class type with a non-trivial destructor ([class.dtor]), the destructor call starts, or- the storage which the object occupies is reused or released.
And this is [basic.types]/3:
For any object (other than a base-class subobject) of trivially copyable type
T
, whether or not the object holds a valid value of typeT
, the underlying bytes ([intro.memory]) making up the object can be copied into an array ofchar
orunsigned char
. If the content of the array ofchar
orunsigned char
is copied back into the object, the object shall subsequently hold its original value. example omitted
The question then becomes, is a memcpy
overwrite of a trivially-copyable class instance "copy construction" or "copy-assignment"? The answer to the question seems to decide whether Meow_internal
is a valid way for a compiler to implement trivially-copyable class Meow
.
If memcpy
is "copy construction", then the answer is that Meow_internal
is valid, because copy construction is reusing the memory. If memcpy
is "copy-assignment", then the answer is that Meow_internal
is not a valid implementation, because assignment does not invalidate pointers to the instantiated members of a class. If memcpy
is both, I have no idea what the answer is.
A trivially copyable class is a class that: has no non-trivial copy constructors, has no non-trivial move constructors, has no non-trivial copy assignment operators, has no non-trivial move assignment operators, and has a trivial destructor.
The function memcpy() is used to copy a memory block from one location to another. One is source and another is destination pointed by the pointer. This is declared in “string.
std::function cannot be TriviallyCopyable (or conditionally TriviallyCopyable ) because as a generic callable object wrapper it cannot assume that the stored callable is TriviallyCopyable .
memmove() is similar to memcpy() as it also copies data from a source to destination.
It is clear to me that using std::memcpy
results in neither construction nor assignment. It is not construction, since no constructor will be called. Nor is it assignment, as the assignment operator will not be called. Given that a trivially copyable object has trivial destructors, (copy/move) constructors, and (copy/move) assignment operators, the point is rather moot.
You seem to have quoted ¶2 from §3.9 [basic.types]. On ¶3, it states:
For any trivially copyable type
T
, if two pointers toT
point to distinctT
objectsobj1
andobj2
, where neitherobj1
norobj2
is a base-class subobject, if the underlying bytes (1.7) making upobj1
are copied intoobj2
,41obj2
shall subsequently hold the same value asobj1
. [ Example:
T* t1p;
T* t2p;
// provided thatt2p
points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in*t1p
contains
// the same value as the corresponding subobject in*t2p
— end example ]
41) By using, for example, the library functions (17.6.1.2)std::memcpy
orstd::memmove
.
Clearly, the standard intended to allow *t1p
to be useable in every way *t2p
would be.
Continuing on to ¶4:
The object representation of an object of type
T
is the sequence of N unsigned char objects taken up by the object of typeT
, where N equalssizeof(T)
. The value representation of an object is the set of bits that hold the value of typeT
. For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values.42
42) The intent is that the memory model of C++ is compatible with that of ISO/IEC 9899 Programming Language C.
The use of the word the in front of both defined terms implies that any given type only has one object representation and a given object has only one value representation. Your hypothetical morphing internal type should not exist. The footnote makes it clear that the intention is for trivially copyable types to have a memory layout compatible with C. The expectation is then that even an object with non-standard layout, copying it around will still allow it to be useable.
In the same draft, you also find the following text, directly following the text you quoted:
For any trivially copyable type
T
, if two pointers toT
point to distinctT
objectsobj1
andobj2
, where neitherobj1
norobj2
is a base-class subobject, if the underlying bytes (1.7) making upobj1
are copied intoobj2
,obj2
shall subsequently hold the same value asobj1
.
Note that this speaks about a change of the value of obj2
, not about destroying the object obj2
and creating a new object in its place. Since not the object, but only its value is changed, any pointers or references to its members should therefore remain valid.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With