问题
I have found the intricacies of trivial types in C++ non-trivial to understand and hope someone can enlighten me on the following.
Given type T
, storage for T
allocated using ::operator new(std::size_t)
or ::operator new[](std::size_t)
or std::aligned_storage
, and a void * p
pointing to a location in that storage suitably aligned for T
so that it may be constructed at p
:
- If
std::is_trivially_default_constructible<T>::value
holds, is the code invoking undefined behavior when code skips initialization ofT
atp
(i.e. by usingT * tPtr = new (p) T();
) before otherwise accessing*p
asT
? Can one just useT * tPtr = static_cast<T *>(p);
instead without fear of undefined behavior in this case? - If
std::is_trivially_destructible<T>::value
holds, does skipping destruction ofT
at*p
(i.e by callingtPtr->~T();
) cause undefined behavior? - For any type
U
for whichstd::is_trivially_assignable<T, U>::value
holds, isstd::memcpy(&t, &u, sizeof(U));
equivalent tot = std::forward<U>(u);
(for anyt
of typeT
andu
of typeU
) or will it cause undefined behavior?
回答1:
No, you can't. There is no object of type
T
in that storage, and accessing the storage as if there was is undefined. See also T.C.'s answer here.Just to clarify on the wording in [basic.life]/1, which says that objects with vacuous initialization are alive from the storage allocation onward: that wording obviously refers to an object's initialization. There is no object whose initialization is vacuous when allocating raw storage with
operator new
ormalloc
, hence we cannot consider "it" alive, because "it" does not exist. In fact, only objects created by a definition with vacuous initialization can be accessed after storage has been allocated but before the vacuous initialization occurs (i.e. their definition is encountered).Omitting destructor calls never per se leads to undefined behavior. However, it's pointless to attempt any optimizations in this area in e.g. templates, since a trivial destructor is just optimized away.
Right now, the requirement is being trivially copyable, and the types have to match. However, this may be too strict. Dos Reis's N3751 at least proposes distinct types to work as well, and I could imagine this rule being extended to trivial copy assignment across one type in the future.
However, what you've specifically shown does not make a lot of sense (not least because you're asking for assignment to a scalar xvalue, which is ill-formed), since trivial assignment can hold between types whose assignment is not actually "trivial", that is, has the same semantics as
memcpy
. E.g.is_trivially_assignable<int&, double>
does not imply that one can be "assigned" to the other by copying the object representation.
回答2:
- Technically reinterpreting storage is not enough to introduce a new object as. Look at the note for Trivial default constructor states:
A trivial default constructor is a constructor that performs no action. All data types compatible with the C language (POD types) are trivially default-constructible. Unlike in C, however, objects with trivial default constructors cannot be created by simply reinterpreting suitably aligned storage, such as memory allocated with std::malloc: placement-new is required to formally introduce a new object and avoid potential undefined behavior.
But the note says it's a formal limitation, so probably it is safe in many cases. Not guaranteed though.
- No. is_assignable does not even guarantee the assignment will be legal under certain conditions:
This trait does not check anything outside the immediate context of the assignment expression: if the use of T or U would trigger template specializations, generation of implicitly-defined special member functions etc, and those have errors, the actual assignment may not compile even if std::is_assignable::value compiles and evaluates to true.
What you describe looks more like is_trivially_copyable, which says:
Objects of trivially-copyable types are the only C++ objects that may be safely copied with std::memcpy or serialized to/from binary files with std::ofstream::write()/std::ifstream::read().
- I don't really know. I would trust KerrekSB's comments.
来源:https://stackoverflow.com/questions/42294487/managing-trivial-types