Выровненный динамический массив и интеллектуальный указатель

Мне часто нужно выровнять начало динамического массива с границей 16, 32 или 64 байта для векторизации, например, для SSE, AVX, AVX-512. Я ищу прозрачный и безопасный способ использования этого в сочетании с интеллектуальными указателями, в частности std::unique_ptr.

Учитывая реализацию подпрограмм распределения и освобождения, скажем

template<class T>
T * allocate_aligned(int alignment, int length)
{
    // omitted: check minimum alignment, check error
    T * raw = 0;
    // using posix_memalign as an example, could be made platform dependent...
    int error = posix_memalign((void **)&raw, alignment, sizeof(T)*length);
    return raw;
}

template<class T>
struct DeleteAligned
{
    void operator()(T * data) const
    {
        free(data);
    }
};

Я хотел бы сделать что-то вроде этого

std::unique_ptr<float[]> data(allocate_aligned<float>(alignment, length));

но я не мог понять, как сделать get unique_ptr, чтобы использовать правильный Deleter, не требуя от пользователя его указывать (что является потенциальной причиной ошибок). Альтернативой, которую я нашел, было использование псевдонима шаблона

template<class T>
using aligned_unique_ptr = std::unique_ptr<T[], DeleteAligned<T>>;

Тогда мы можем использовать

aligned_unique_ptr<float> data(allocate_aligned<float>(alignment, length));

Оставшаяся проблема заключается в том, что ничто не мешает пользователю помещать необработанный указатель в std::unique_ptr.

Кроме того, вы видите что-то не так с этим? Есть ли альтернатива, которая менее подвержена ошибкам, но полностью прозрачна для пользователя после того, как было выполнено распределение?

Ответ 1

Вы никогда не должны возвращать собственный указатель raw. allocate_aligned нарушает это. Измените его, чтобы вместо этого использовать соответствующий умный указатель:

template<class T>
std::unique_ptr<T[], DeleteAligned<T>> allocate_aligned(int alignment, int length)
{
    // omitted: check minimum alignment, check error
    T * raw = 0;
    // using posix_memalign as an example, could be made platform dependent...
    int error = posix_memalign((void **)&raw, alignment, sizeof(T)*length);
    return std::unique_ptr<T[], DeleteAligned<T>>{raw};
}

Таким образом, ни один клиент не может помещать необработанный указатель в неправильный интеллектуальный указатель, потому что он никогда не получает необработанный указатель. И вы предотвращаете утечку памяти из-за того, что случайно не поместили необработанный указатель в умный.

Как отметил @KonradRudolph, сам стандарт идет именно так: в С++ 14 std::make_unique - это именно такая оболочка для простого new.