Pergunta

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.

Foi útil?

Solução

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.

Outras dicas

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();
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top