Инициализация N std::unique_ptr с пользовательским удалителем в функции шаблона

#c #templates #std

Вопрос:

Мне нужно сопоставить адреса периферийной памяти для доступа к оборудованию в Linux. Чтобы сделать вещи немного более общими (они же сложные…) Я создал следующую функцию:

 template<typename Tp>
static Tp* getPeripheralPtr(unsigned address)
{
    static constexpr auto deleter = [](Tp* ptr){ munmap(ptr, sizeof(Tp); };
    static std::unique_ptr<Tp> ptr(nullptr, deleter);

    if (!ptr)
    {
        /* mmap logic */
        ptr.reset(reinterpret_cast<Tp*>(virtaddr));
    }

    return ptr.get();
}
 

где Tp — структура, представляющая регистры.

Это сработало довольно хорошо, хотя позже в процессе я понял, что у меня может быть несколько контроллеров, например PWM0, PWM1 с одинаковым расположением регистров, только со смещением. Поэтому я переписал функцию:

 template<typename Tp, size_t nControllers = 1, size_t offset = 0>
static Tp* getPeripheralPtr(unsigned address, int idx = 0)
{
    static Storage<Tp, nControllers> storage;

    if (!storage.ptr[idx])
    {
        /* mmap magic */
    }

    return storage.ptr[idx].get();
}
 

Мне нужен был способ инициализации N экземпляров std::unique_ptr, поэтому я создал вспомогательный класс хранения:

 template<typename Tp, std::size_t N>
class Storage
{
    static constexpr auto deleter = [](Tp* ptr) { munmap(ptr, sizeof(Tp)); };

    template<size_t ... indices>
    explicit Storage(std::index_sequence<indices...> constamp;)
        : ptr{{((void)indices, ptr_type(nullptr, deleter))...}}
    {
    }
public:
    using ptr_type = std::unique_ptr<Tp, decltype(deleter)>;
    ptr_type ptr[N];

    Storage() : Storage(std::make_index_sequence<N>{}) {}
};
 

Но это дает мне ошибку, когда nControllers > 1 сообщает об отсутствии соответствующего конструктора для инициализации «Хранилища><int, 2>::ptr_type». Не могли бы вы, пожалуйста, указать мне правильное направление?

Комментарии:

1. Используйте std::array для этого вместо C-массива.

2. ух ты, это сработало.. Но мне интересно.. Почему это так? Что изменилось в использовании std::array? Если вы сформулируете это в реальном ответе, я приму и поддержу 🙂

3. При инициализации необработанного массива у вас есть лишняя пара фигурных скобок. Если, конечно, мои глаза не слишком устали.

4. @Рассказчик-UnslanderMonica, черт возьми. Наверное, я слишком устал для этого. Но это заставляет меня задуматься… Почему это сработало с std::массивом с дополнительными фигурными скобками… Что ж, большое вам спасибо за решение этой проблемы!

5. A std::array -это в основном структура с общедоступным необработанным элементом массива. Таким образом, внешние фигурные скобки инициализировали структуру как совокупность, а внутренние-открытый элемент.

Ответ №1:

С этим:

 ptr{{((void)indices, ptr_type(nullptr, deleter))...}}
 

Вам нужен тип внутри initializer_list конструктора. И очевидно, что вы не могли бы использовать только необработанный C-массив. Если вы удалите один уровень фигурных скобок, вы можете использовать его с необработанным C-массивом.