3

Do the following class break the strict aliasing rule:

template<typename T>
class store {
    char m_data[sizeof(T)];
    bool m_init;
public:
    store() : m_init(false) {}
    store(const T &t) : init(true) {
        new(m_data) T(t);
    }
    ~store() {
        if(m_init) {
            get()->~T();
        }
    }
    store &operator=(const store &s) {
        if(m_init) {
            get()->~T();
        }
        if(s.m_init) {
            new(m_data) T(*s.get());
        }
        m_init = s.m_init;
    }
    T *get() {
        if (m_init) {
            return reinterpret_cast<T *>(m_data);
        } else {
            return NULL;
        }
    }
}

My reading of a standard is that it is incorrect but I am not sure (my usage is to have an array of objects T + some metadata of those objects but to have control over the object construction/deconstruction without manually allocating memory) as the allocated objects are used as examples for placement new in standard.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Maciej Piechotka
  • 7,028
  • 6
  • 39
  • 61

2 Answers2

4

The standard contains this note:

[ Note: A typical implementation would define aligned_storage as:

template <std::size_t Len, std::size_t Alignment>
struct aligned_storage {
  typedef struct {
    alignas(Alignment) unsigned char __data[Len];
  } type;
};

end note ]

                                                                   — Pointer modifications [meta.trans.ptr] 20.9.7.5/1

And aligned_storage is defined in part with:

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

The only property covered by the standard that restricts the addresses at which an object can be constructed is alignment. An implementation might have some other restrictions, however I'm not familiar with any that do. So just ensure that having correct alignment is enough on your implementation and I think this should be okay. (and in pre-C++11 compilers you can use use compiler extensions for setting alignment such as __attribute__((alignment(X))) or __declspec(align(X)).

I believe that as long as you don't access the underlying storage directly the aliasing rules don't even come into the picture, because the aliasing rules cover when it is okay to access the value of an object through an object of a different type. Constructing an object and accessing only that object doesn't involve accessing the object's value through an object of any other type.

Earlier answer

The aliasing rules specifically allow char arrays to alias other objects.

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

[...]

— a char or unsigned char type.

                                                                   — Lvalues and rvalues [basic.lval] 3.10/10

You do need to make sure that the array is properly aligned for type T though.

alignas(T) char m_data[sizeof(T)];

The above is C++11 syntax for setting alignment, but if you're on a C++03 compiler then you'll need a compiler specific attribute to do the same thing. GCC has __attribute__((aligned(32))) and MSVC has __declspec(align(32))


Kerrek SB brings up a good point that the aliasing rules state that it's okay to access the value of a T object via a char array, but that may not mean that accessing the value of a char array via a T object is okay. However, if the placement new expression is well defined then that creates a T object which I think it's okay to accesses as a T object by definition, and reading the original char array is accessing the value of the created T object, which is covered under the aliasing rules.

I think that implies that you could store a T object in, for example, an int array, and as long as you don't access the value of that T object through the original int array then you're not hitting undefined behavior.

bames53
  • 86,085
  • 15
  • 179
  • 244
0

What is allowed is to take a T object and interpret it as an array of chars. However, it is in general not allowed to take an arbitrary array of chars and treat it as a T, or even as the pointer to an area of memory containing a T. At the very least, your char array would need to be properly aligned.

One way around this might be to use a union:

union storage { char buf[sizeof(T)]; T dummy; };

Now you can construct a T inside storage.buf:

T * p = ::new (storage.buf) T();
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • @MaciejPiechotka in C++11 union members aren't required to have trivial constructors. (however it's still UB to read a union member other than the last member written) – bames53 Jun 09 '12 at 23:45
  • @bames53: I know. The problem is that I'm using C++03 compilers. – Maciej Piechotka Jun 09 '12 at 23:48
  • You could also do dynamic_cast if you wanted a pointer to the class-stored-in-a-character-buffer. but there's all sorts of things wrong with that. – std''OrgnlDave Jun 10 '12 at 00:20
  • @bames53: I'm not suggesting to ever access the `dummy` element. It's just there to give the entire union the correct alignment. You'd say `new (storage.buf) T` etc. and only ever use `buf`. – Kerrek SB Jun 10 '12 at 00:40
  • @KerrekSB: Wouldn't it be better ot avoid `buf` at all in union (have say `union U { char dummy; T obj; U() : dummy(0) {}}` and use `obj` for placement new (`new(&obj) T(...)`)? – Maciej Piechotka Jun 10 '12 at 04:55
  • @MaciejPiechotka: I'm not entirely sure. It's probably OK, as both your `dummy` and your `obj` have the same address. I just wanted to avoid making reference to the unused `T` object altogether. – Kerrek SB Jun 10 '12 at 11:06
  • @jthill: Can you be more specific please? – Kerrek SB Jun 10 '12 at 11:09
  • You cannot treat objects with nontrivial structors as char arrays; you can't for instance `memcpy()` them and expect the copy to have the same value. There is no need for the union here, because alignment requirements depend only on object size and the T is the first member. "At the very least" implies other concerns; there are none. – jthill Jun 10 '12 at 15:01
  • @jthill: I'm never proposing to memcopy anything. The memory is solely to be used with placement-new. Unions need to be aligned for all members, so the union's `buf` is different from a naked char array. Finally, I say "at least" because there are further considerations in general when you interpret one piece of memory as multiple different types such as trapping representations. – Kerrek SB Jun 10 '12 at 17:07