Как использовать shared_ptr и make_shared с массивами?

Я хочу использовать C++ shared_ptr в качестве замены необработанных указателей C. В качестве простого примера следующий код работает так, как задумано:

from libcpp.memory cimport shared_ptr, allocator

cdef shared_ptr[double] spd 
cdef allocator[double] allo
spd.reset(allo.allocate(13))

Размер выбран здесь равным 13, но обычно он неизвестен во время компиляции. Я не уверен, что это правильно, но у меня не было никаких ошибок (пока нет утечек памяти и segfaults). Мне любопытно, есть ли более оптимальное решение с make_shared.

Но я не могу использовать массивы С++ 11, потому что Cython не позволяет использовать литералы в качестве шаблонов, например. что-то вроде

cdef shared_ptr[array[double]] spd = make_shared[array[double,13]]()

и обычные массивы, которые должны работать с компилятором С++ 20 (например, gcc 10), так или иначе вызывают проблемы:

# Cython error "Expected an identifier or literal"
cdef shared_ptr[double[]] spd = make_shared[double[]](3)    

# Can't get ptr to work
ctypedef double[] darr
cdef shared_ptr[darr] spd = make_shared[darr](13)
cdef double* ptr = spd.get()    # or spd.get()[0] or <double*> spd.get()[0] or ...

Является ли решение распределителя правильным и лучшим или есть другой способ сделать это?


person oli    schedule 04.08.2020    source источник
comment
Ваше решение «работает» (вероятно, это UB) для двойников, но приведет к утечке памяти для более сложных классов, так как shared_ptr уничтожит только первый объект, но не остальные 12. Почему бы не использовать std::vector‹double› вместо весь shared_ptr-бизнес?   -  person ead    schedule 04.08.2020
comment
Кстати, массив не завернут в cython, так что, возможно, вы сделали это неправильно? Предоставьте минимальный воспроизводимый пример.   -  person ead    schedule 04.08.2020
comment
Насколько мне известно, класс std::array нельзя использовать, если размер неизвестен во время компиляции. И даже если это довольно хакерское решение (stackoverflow.com/questions/36357024/).   -  person oli    schedule 05.08.2020
comment
У меня есть несколько случаев использования, когда я хотел бы избежать копирования данных, когда функция возвращает указатель (следовательно, без векторов), но все же нужен какой-то подсчет ссылок (с указателями C я делаю это вручную, shared_ptr упростит это) .   -  person oli    schedule 05.08.2020


Ответы (1)


Вот что я собираюсь с

cdef extern from *:
    """
    template <typename T>
    struct Ptr_deleter{
        size_t nn;
        void (*free_ptr)(T*, size_t);
        Ptr_deleter(size_t nIn, void (*free_ptrIn)(T*, size_t)){
            this->nn = nIn;
            this->free_ptr = free_ptrIn;
        };
        void operator()(T* ptr){
            free_ptr(ptr, nn);
        };
    };
    template <typename T>
    std::shared_ptr<T> ptr_to_sptr (T* ptr, size_t nn, void (*free_ptr)(T*, size_t)) {
        Ptr_deleter dltr(nn,  free_ptr);
        std::shared_ptr<T> sp (ptr, dltr);
        return sp;
    };
    """
    shared_ptr[double] d_ptr_to_sptr "ptr_to_sptr"(double* ptr, size_t nn, void (*free_ptr)(double*, size_t) nogil) nogil

cdef void free_d_ptr(double* ptr, size_t nn) nogil:
    free(ptr)

cdef shared_ptr[double] sp_d_empty(size_t nn) nogil:
    return d_ptr_to_sptr(<double*> nullCheckMalloc(nn*sizeof(double)), nn, &free_d_ptr)

Насколько я понимаю, правильный способ обработки массивов malloced — использовать настраиваемый модуль удаления, как я. Лично я предпочитаю придерживаться несколько необработанных указателей C (double* вместо double[] или что-то в этом роде), так как это более естественно в Cython и моих проектах.

Я думаю, достаточно легко увидеть, как изменить free_ptr для более сложных типов данных. Для простых типов данных это можно было бы сделать меньшим количеством строк и менее запутанным, но я хотел иметь ту же основу.

Мне нравится мое решение в том смысле, что я могу просто обернуть существующие необработанные указатели кода Cython/C в файл shared_ptr.

При работе с C++ (особенно с более новыми стандартами, такими как C++20) я думаю, что дословный код довольно часто необходим. Но я намеренно определил free_d_ptr в Cython, поэтому можно легко использовать существующий код Cython для выполнения фактической работы по освобождению/очистке/независимо от массива.

Я не заставил C++11 std::arrays работать, и, по-видимому, это вообще невозможно в Cython (см. cython">Взаимодействие массива C++11 с Cython).

Я не получил double[] или что-то подобное для работы (возможно в С++ 20), но с дословным кодом С++ я думаю, что это должно быть выполнимо в Cython. В любом случае, как я уже сказал, я предпочитаю больше C-подобных указателей/массивов.

person oli    schedule 08.08.2020