3

So basically, this is what I've got so far. I have a mat4 class, which is made up of __m128's and need to be aligned on a 16-byte boundry:

_MM_ALIGN16
class mat4
{
   ...
};

I have another class which itself is not aligned, but contains a mat4.

class OtherClass
{
private:
   mat4 matrix;
   // Other data whose alignment doesn't really matter
   ...
};

I need to dynamically allocate instances of OtherClass, ala:

OtherClass* stuff = new OtherClass[n];

How can I guarantee that the mat4 inside the instance(s) will be properly aligned, while still calling mat4's constructor?

I can (and generally prefer to) use C++11's features, perhaps aligned_storage is what I'm looking for? How would I use it in this case?

Haydn V. Harach
  • 1,265
  • 1
  • 18
  • 36
  • I've never done this, but it might be worth overloading `operator new` for your class and using an aligned memory allocator (such as `_aligned_malloc` on Visual C++, or a generic version based on regular `operator new`) underneath. – user541686 Feb 17 '14 at 07:11
  • `_aligned_malloc` wont call the constructor. I would really rather not have to overload `operator new` on every class that happens to contain a `mat4`.... – Haydn V. Harach Feb 17 '14 at 07:20
  • 1
    `operator new` (which is different from the `new` operator) doesn't call the constructor either, and in fact it shouldn't. But your second sentence is a legitimate concern... – user541686 Feb 17 '14 at 07:30
  • One way is to replace the default new operator with the largest alignment you need for all your C++ objects... – Alexis Wilke Feb 18 '14 at 00:28

3 Answers3

2

You may use placement new to decouple memory allocation from object creation:

/* Memory allocation */
#ifdef _MSC_VER
     void *buffer = _aligned_malloc(n * sizeof(OtherClass), 16);
#else
     void *buffer = memalign(16, n * sizeof(OtherClass));
#endif
/* Object construction */
OtherClass *array = new (buffer) OtherClass[n];

/* Use your objects */
...

/* Object destruction */
for (size_t i = 0; i < n; i++) {
    array[i].~OtherClass();
}
/* Memory deallocation */
#ifdef _MSC_VER
     _aligned_free(buffer);
     buffer = nullptr;
#else
     free(buffer);
     buffer = nullptr;
#endif

A more C++ish way would be to use std::vector with an aligned allocator.

Marat Dukhan
  • 11,993
  • 4
  • 27
  • 41
2

You can use std::aligned_storage in order to define an uninitialized storage with the specified alignment in conjunction with placement new operator.

For example:

class OtherClass
{
private:
    using storage_t = typename std::aligned_storage<sizeof(mat4), 16>::type;

    storage_t _storage;

    mat4* address()  {
        return static_cast<mat4*>(static_cast<void*>(&_storage));
    }

    mat4 const* address() const {
        return static_cast<mat4 const*>(static_cast<void const*>(&_storage));
    }

public:

    OtherClass() { 
        new(address()) mat4();
    }
    OtherClass(OtherClass const& rhs) {
        new(address()) mat4(*rhs.address());
    }
    OtherClass& operator=(OtherClass const& rhs) {
        *address() = *rhs.address();
    }

    ~OtherClass() { 
       address()->~mat4(); 
    }  

    // setter
    template<typename... Args,
        class Enable = typename std::enable_if<
            std::is_constructible<mat4, Args...>::value
        >::type
    >
    void setMat(Args&&... args) {
        new(address()) mat4(std::forward<Args>(args) ...);
    }

    // getter
    mat4 const& getMat() const {
        return *address();
    }

    mat4& getMat() {
        return *address();
    }
    ...
};

see also: std::aligned_storage

Similar question on SO: C++ Allocate Storage for Object without Initializing it?

Community
  • 1
  • 1
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • Would it be best to do this to allocate/deallocate `__m128`'s in the constructor/destructor of the `mat4` class? – Haydn V. Harach Feb 17 '14 at 18:01
  • @MooingDuck Well, if __m128 should be aligned, and mat4 is basically and array of N __m128, we certainly can implement "mat4" similar as shown above. We just need storage for N `__mat128`s, e.g. `typename std::aligned_storage::type _storage[N];` where each element is 16 byte aligned. – CouchDeveloper Feb 17 '14 at 18:19
  • @HaydnV.Harach Note that `_storage` is _uninitialized_ storage. That is, you need "placement new operator" to construct (either copy or move) one `__m128` element. And likewise, you need to call the destructor explicitly for each element in order to destroy a `__m128`. IMO, it would be best to _initialize_ the storage in the Constructor and _destroy_ it in the Destructor. – CouchDeveloper Feb 17 '14 at 18:24
  • @CouchDeveloper: My bad, I thought the OP's class was called `__m128` instead of `mat4`. I'd meant that this logic should be in the `mat4` class – Mooing Duck Feb 17 '14 at 18:24
  • @MooingDuck Of course, wherever it is more suitable :) – CouchDeveloper Feb 17 '14 at 18:25
  • @CouchDeveloper: I added the missing copy constructor and copy assignment, since those were obvious errors. I think `getMat` should return by `mat4 const&`, but that's subjective so did not make that change. – Mooing Duck Feb 17 '14 at 18:27
  • @MooingDuck Yes, copy-ctor, and assignment operator is required. For the "get" accessor, we could also return a reference _and_ a const_reference. – CouchDeveloper Feb 17 '14 at 18:33
0

union alignas (16) { __m128 _v; float _s[4]; }; - as an example, with the usual caveats about strict aliasing - doesn't provide a guarantee for heap storage alignment.

You may be able to avoid a new / delete implementation by querying the std::max_align_t type, as described in this answer.

Otherwise, you may need to re-implement the global operator new / delete functions. I don't think per-class methods are sufficient. e.g., consider a class Camera that has mat4 member; If a Camera is instanced on the stack, then the mat4 member alignment should be preserved. If a Camera object is created on the heap, I don't see how this can be otherwise enforced.

Community
  • 1
  • 1
Brett Hale
  • 21,653
  • 2
  • 61
  • 90
  • Because __m128 isn't a class, it's a special built-in data type for SSE registers. – Haydn V. Harach Feb 17 '14 at 18:02
  • Sorry, I thought the OP's class was `__m128` instead of `mat4`. Why do you have a union here? I don't understand how you intend the OP to apply this answer to his code. – Mooing Duck Feb 17 '14 at 18:23