3

C++17 introduced a new type, std::byte, so now we finally have a first-class citizen type to represent bytes in memory. Besides being a novelty in the standard, the C++ rules for object creation, start and end of life, aliasing etc. are fairly complicated an unintuitive most of the times, so whenever I feel std::byte is the right tool I also get nervous and reluctant to use it, for fear of unintentionally summoning the Undefined Behavior Balrogs.

One such case is a buffer to be used with placement new:

#include <memory>
#include <cstddef>
#include <type_traits>

struct X { double dummy[4]; char c; };

auto t1()
{
    // the old way

    std::aligned_storage_t<sizeof(X)> buffer;
    X* x = new (&buffer) X{};

    x->~X();
}

auto t2()
{
    // the new way?

    std::byte buffer[sizeof(X)];
    X* x = new (&buffer) X{};

    x->~X();
}

Is t2 perfectly safe and equivalent with t1?

In response to alignment issues, what about:

auto t3()
{
    alignas(X) std::byte buffer[sizeof(X)];

    X* x = new (&buffer) X{};
    x->~X();
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
bolov
  • 72,283
  • 15
  • 145
  • 224

2 Answers2

9

Is t2 perfectly safe and equivalent with t1?

No. In fact, both are bad.

t2 is bad for the reason NathanOliver indicates: it's underaligned. You'd need to write:

alignas(X) std::byte storage[sizeof(X)];

t1 also kind of has this problem, in that you almost certainly want to write aligned_storage_t<sizeof(X), alignof(X)> and not just aligned_storage_t<sizeof(X)>. If X were overaligned, you would lose that here. If X was just large but had no alignment requirement, you would end up with a relatively overaligned storage.

t1 is also bad for an especially peculiar reason: aligned_storage doesn't quite guarantee what you think it guarantees. In particular, it guarantees that an X can fit in aligned_storage<sizeof(X)>, but it does not guarantee that it can fit exactly. The specification is simply:

The member typedef type shall be a trivial standard-layout type suitable for use as uninitialized storage for any object whose size is at most Len and whose alignment is a divisor of Align.

That is, aligned_storage<16>::type is guaranteed to be at least 16 bytes, but a conforming implementation could easily give you 32. Or 4K. That in addition to the problem of using aligned_storage<16> by accident instead of aligned_storage_t<16>.

This is why P1413 exists as a paper: aligned_storage is kind of bad.


So the real answer is actually just to write something like libstdc++'s __aligned_membuf:

template <typename T>
struct storage_for {
    alignas(T) std::byte data[sizeof(T)];

    // some useful constructors and stuff, probably some getter
    // that gives you a T* or a T const*
};
Barry
  • 286,269
  • 29
  • 621
  • 977
4

Is t2 perfectly safe and equivalent with t1?

No. std::aligned_storage creates storage that is suitably aligned for the object being placed in it. std::byte buffer[sizeof(X)] while being the correct size, has the alignment of std::byte. This normally will not have same alignment of the type you are placing in it since it has an alignment of 1.

This is not an issue as far as the C++ virtual machine is concerned, but in the real world this can cause sever performance penalties or even cause the program to crash.

If you want suitably aligned storage, use std::aligned_storage see Barry's answer

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 2
    spitballing here: what about `alignas(alignof(X)) std::byte buffer[sizeof(X)];`? – bolov Oct 08 '19 at 14:25
  • @bolov That would be fine then. That's a lot more typing than `std::aligned_storage_t buffer` though. – NathanOliver Oct 08 '19 at 14:26
  • @NathanOliver: But it would be potentially better, since `alignof(X)` may be far less than the default alignment that `aligned_storage` gives you without specifying an alignment. – Nicol Bolas Oct 08 '19 at 14:27
  • @NicolBolas For that you can use `std::aligned_storage_t buffer` – NathanOliver Oct 08 '19 at 14:29
  • Although now the characters to type is about the same. I still like it though as the name gives it context and meaning. – NathanOliver Oct 08 '19 at 14:30
  • 1
    @NathanOliver: Also, `aligned_storage/union` [are being deprecated](https://wg21.link/P1413) in C++20 or 23. – Nicol Bolas Oct 08 '19 at 14:32
  • @NicolBolas Are you sure they are going to be? I see there is a paper requesting it but it is not part of C++20 (yet) and not all papers are actually accepted. This seems like it would be a large breaking change for not much or any gain. – NathanOliver Oct 08 '19 at 14:37
  • @NathanOliver: Deprecation, by definition, is not a breaking change; it's a declaration of the *intent* to remove in the future. It may not be part of C++20, but given that it was forwarded with near-unanimous votes to LEWG, that seems like a good sign that it's going to happen sooner or later. – Nicol Bolas Oct 08 '19 at 14:40
  • @NicolBolas Okay. Thanks for the information. Guess I'll have to stop suggesting it. – NathanOliver Oct 08 '19 at 14:40