2

Consider the following class:

template <class T>
class defer {
public:
    template <class ...Args>
    void construct(Args&&...);
    T& obj();
    ~defer();
private:
    std::uint8_t memory[sizeof(T)];
    T * ptr();
};

template <class T>
template <class ...Args>
void defer<T>::construct(Args&& ...args) {
    new(static_cast<void*>(&memory[0])) T(std::forward<Args>(args)...);
}

template <class T>
T& defer<T>::obj() {
    return *(ptr());
}

template <class T>
defer<T>::~defer() {
    ptr()->~T();
}

template <class T>
T * defer<T>::ptr() {
    return static_cast<T*>(&memory[0]);
}

NOW I KNOW that there are issues with this, but in order to make the code short for purposes of discussion we are going to assume that the defer::construct() is always called before the object goes out of scope.

That being said, is it always necessarily safe to do this? Or can in some weird corner case of multiple virtual inheritance with other craziness can std::uint8_t[sizeof(T)] not allocate enough space?

Robert Mason
  • 3,949
  • 3
  • 31
  • 44
  • 8
    `std::uint8_t[sizeof(T)]` will always have enough space. But... it may not always have the right alignment (`std::aligned_storage` can help there). – R. Martinho Fernandes Oct 25 '12 at 15:50
  • `uint8_t` is **optional**. Use `uint_least8_t` or `uint_fast8_t`; they will always exist. – Pete Becker Oct 25 '12 at 17:04
  • @Pete Becker: Yes, but do you know of any platforms that do not have 8-bit integral types? Also, I know that on some platforms uint_least8_t and uint_fast8_t are both typedefs to int for efficiency purposes, which could mean a 300% memory overhead. `std::uint8_least8_t[sizeof(T)/sizeof(std::uint_least8_t)+((sizeof(T) % sizeof(std::uint_least8_t) != 0) ? 1 : 0)]` works, but is a tad ugly for my taste... – Robert Mason Oct 25 '12 at 17:19
  • `uint_least8_t` is required to be a synonym for an unsigned type with at least 8 bits "such that no unsigned type with lesser size has at least the specified width". (C standard, 7.20.1.2/2). If your implementation defines it as `int` when there are smaller types available, it not only has the wrong properties (because it's a signed type), but it's too big, hence doesn't conform to the standard. – Pete Becker Oct 25 '12 at 17:38
  • As to non-8-bit systems, see http://stackoverflow.com/questions/5516044/system-where-1-byte-8-bit. – Pete Becker Oct 25 '12 at 17:41
  • sorry, that is correct - typedef to int is only conformant on uint_fast*_t. I still think it's uglier, but I guess it's better to be maximally portable. – Robert Mason Oct 26 '12 at 18:07

1 Answers1

12

R. Martinho Fernandes beat me to it! Use

typename std::aligned_storage<sizeof(T)>::type  memory;

and you're good to go. See here for details.


As our panel of commentators has pointed out, the default alignment is always enough, but may be more stringent than required for your type (so you waste space with extra padding). You can avoid this by just specifying it explicitly:

typename std::aligned_storage<sizeof(T), alignof(T)>::type memory;
Useless
  • 64,155
  • 6
  • 88
  • 132
  • Haha, for a moment there you made the usual mistake of forgetting `::type` :P – R. Martinho Fernandes Oct 25 '12 at 15:53
  • 2
    What about `aligned_storage` ? Not a big deal, but might save padding in the case where `T` doesn't need the default (maximum) alignment. – Steve Jessop Oct 25 '12 at 16:14
  • The default alignment is "the most stringent alignment requirement for any C++ object type whose size is no greater than" the called-for length. Similar to the alignment requirements when allocating a `char` array. In some cases you don't need such a strict alignment; e.g. if T is int[2] then you only need alignment for an int, but the default-alignment would be sufficient for any object of twice that size. – bames53 Oct 25 '12 at 16:22
  • Cheers - fixed the alignment description. – Useless Oct 25 '12 at 16:28
  • `aligned_storage` may already use it internally, so it's not clear you gain anything, but it should work. – Useless Oct 25 '12 at 21:19
  • @bames53: even worse, if `T` is `int[3];` and there's a `2*sizeof(int)`-aligned type in the implementation, then that excess alignment would cause your object to be 1/3 bigger than it needs to be. – Steve Jessop Oct 26 '12 at 08:29
  • @RobertMason You can use alignas on its own like: `alignas(int) char[sizeof(int)];` but it's not needed in conjunction with alligned_storage. – bames53 Oct 26 '12 at 15:45