LINUX.ORG.RU

Как привести между собой void* и std::iterrator?

 


0

3

Приветствую.

Нужно в асинхронную С либу отправлять разные типы данных без копирования памяти, которая делает обратный вызов и в нем уже можно освободить.

Первое что приходит в голову сделать объект вида std::list<std::shared_ptr<T>>, при вызове передавать итератор, а при обратном удалять итератор из std::list, но подцепить к вызову естественно можно только void* (никаких преобразование конечно внутри либы с этим указателем не делается).

Как сделать преобразование типов между std::list<std::shared_ptr<T>>::iterator и void*? или как то по другому это делается (не нагуглил)?

★★★★

Последнее исправление: Dimez (всего исправлений: 2)
Ответ на: комментарий от wolverin

получается придется передавать указатель на объект, а потом по нему делать поиск в списке и уже тогда получится удалить итератор

wolverin ★★★★
() автор топика

Всему, что ты передаёшь как opaque в колбеки, ты обязан обеспечить время жизни от передачи колбеку, до окончании его работы/работ.

Если отбросить всё, что после первого абзаца и оставить только:

Нужно в асинхронную С либу отправлять разные типы данных без копирования памяти, которая делает обратный вызов и в нем уже можно освободить.

То про итераторы забудь: хрен ты обеспечишь их валидное состояние, когда будешь по ним удалять данные.

Я предположу, что C-апи может иметь вид:

void async_send(const void* data, size_t size, void(*completion_token)(void *opaque), void *opaque);

т.е. до вызова completion_token ты обязан обеспечить существование и валидное состояние data и opaque, если последнее не nullptr.

Ну а дальше, вариантов много. Так как в таком виде, у тебя completion_token принимает только opaque, но не сами данные, то opaque должен быть связан с данными.

Можно сделать грубо как-то так:

// Исходим, что данные у тебя в shared_ptr<T> хранятся
template<class T>
struct data_deleter {

  static void cleanup(void *opaque) {
    static_cast<data_deleter*>(opaque)->cleanup();
  }

  static data_deleter* create(std::shared_ptr<T> data) {
    return new data_deleter(std::move(data));
  }

private:
    // да, делаем трюк, что бы создавать только в куче
    data_deleter(std::shared_ptr<T> data) : _data(std::move(data)) {}

  void cleanup() {
    // как-то чистим ресурс, можно вот так вот сурово:
    delete this;
    // удалим себя: это безопасно, так как мы обеспечили трюк с выделением только в куче
    // больше ничего после не вызываем, а счётчик ссылок в data уменьшится на 1
  }

  std::shared_ptr<T> _data{};
};

Ну и вызывать как-то так:


std::shared_ptr<Foo> data;
...

async_send(data.get(), sizeof(*data.get()), &data_deleter::cleanup, data_deleter::create(data));

Проблемы:

  1. аллокации в куче
  2. если API допускает, что колбек может быть не вызван - утечка памяти.

Второй вариант, это хранить opaque в контейнере в неком враппере над async_send. Но тут нужно смотреть, как поиск делать. Проще гарантировать отсутсвие пункта 2, но по части пункта 1, на вскидку, особо идей нет. Скорее складывать data_deleter без создания в куче в дек, кроме того, сохранять референс на parent и пробегать в cleanup по коллекции, искать себя по совпадению адреса и делать удаление.

Куда проще, если колбек имеет вид:

void(*completion_token)(void *opaque, const void* data, size_t size);

В таком случае, в качестве opaque задавать this класс-обёртки и его метод для чистки, а уже по указателю на данные делать поиск.

hatred ★★★★
()

Вам здесь вместо std::list нужен какой-то интрузивный список, который гарантирует, что итератор и есть указатель на текущий элемент списка. Возможно, это будет самодельный интрузивный список, если под рукой нет готового.

Тогда вы можете приводить указатель на конкретный элемент списка к void* и обратно. И имея указать на конкретный элемент списка сможете удалять его при необходимости.

eao197 ★★★★★
()

Как привести между собой void* и std::iterrator ?

Задавай правильные вопросы: какую задачу ты решаешь. В заголовке с ходу некий «дуинг ит вронг» достойный перлов царя.

slackwarrior ★★★★★
()
Ответ на: комментарий от hatred

спасибо, приблизительно так и начал уже делать - отделить данные от обработки и данные только в шаредптр засунуть, правда данные не через шаблон, а наследование с виртуальными деструкторами, т.к. по разному память освобождать…

вообщем где то так пока

wolverin ★★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 1)

У std::shared_ptr есть метод get, который возвращает сырой указатель, который можно кастовать в void*. Разумеется, чтобы никто не освободил память и указатель оставался валидным, shared_ptr должен где-то хранится до того момента, когда callback уже точно не будет вызван.

Либо всё ещё проще - умные указатели это сахар для C++ кода, при вызове C кода делаешь обычный new и передаёшь этот указатель. А при вызове твоего callback, делаешь delete. Это нормально, так как ты в любом случае делаешь ручное освобождение, не важно это удаление из std::list с автоматическим вызовом деструктора shared_ptr или ручной delete. Callback забудут вызвать или ты забудешь внутри callback сделать операцию направленную на освобождение указателя - будет утечка. RAII не работает в этом сценарии. Танцы с оборачниваем утечки в std::list не особо имеют смысла.

Прочитал внимательнее твой пост. Не надо конвертировать итератор в указатель. Нет никаких гарантий, что структура итератора равна по размеру указателю (и скорее всего она больше). То есть итератор в общем случае непредставим в виде void*. Разве что делать new и перемещать итератор в кучу, а потом ему делать delete, но это явный over-engineering. Опять же не проще ли делать new и delete с самим объектом без всяких std::list и итераторов.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 7)

std::list guarantees total pointer, reference, and iterator stability during almost all modifications.

Pointers or iterators are only invalidated in a few explicit scenarios: direct erasure of element and container destruction.

Компилируется и работает:

#include <list>
#include <stdio.h>

int main()
{
    std::list<int> a;
    a.push_back(1);
    a.push_back(2);
    std::list<int>::iterator it = a.begin();
    int *ptr = &*it;
    printf("%d\n", *ptr);
    return 0;
}

shdown ★★
()
Ответ на: комментарий от shdown

Проблема в том, что отсутствует гарантия, что sizeof(итератор) <= sizeof(void*). То есть итератор тупо непредставим в виде void*. И от его стабильности нет толку. Единственное решение как протащить итератор через void* - выделять память под итератор в куче и перемещать итератор туда (а потом не забыть освободить память через delete). Но это лишает всю затею смысла, ведь можно то же самое проделывать с самим объектом отказавшись от умных указателей, которые плохо сочетаются с сишным кодом.

RAII не работает, когда циклом жизни твоих объектов по сути управляет внешний код (пока callback не вызовут, освобождения ресурсов не будет).

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от KivApple

Проблема в том, что отсутствует гарантия, что sizeof(итератор) <= sizeof(void*).

Ерунда какая-то. У итератора есть operator *(). Он возвращает ссылку. Ссылку можно сконвертировать в указатель с помощью &:

    std::list<int>::iterator it = a.begin();
    int *ptr = &*it;
shdown ★★
()
Ответ на: комментарий от shdown

Ты не можешь из указателя на элемент за O(1) сделать обратно итератор для std::list (для std::vector можешь, но там нет гарантий стабильности указателей). То есть для erase тебе потребуется std::find, который за O(N) будет искать твой элемент. То есть преобразование итератора в указатель происходит с потерей информации (восстановимой, но всё же).

Если бы не была нужна возможность восстановиь итератор, то можно было бы вообще сделать get у shared_ptr и не париться (ну или &**).

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от KivApple

не проще ли делать new и delete с самим объектом без всяких std::list и итераторов.

от итераторов уже отказался, нью и делете делаю над классом обработчика внутри которого шаредпрт, т.к. не знаю кто последний освободит память - обратный С вызов или вызов откуда был сделан этот вызов )

wolverin ★★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 1)
Ответ на: комментарий от wolverin

почти всегда shared_ptr реализовано на атомиках(ну то есть стандарт допускает и мутексы но gcc и clang использует атомики), имеется ввиду счетсик ссылок

sni4ok
()
Ответ на: комментарий от wolverin

если спросить языковую модель про скорость копирования атомика по сравнению со скоростью копирования указателя она возможно допустимо ответит, но если спросить языковую модель про shared_ptr она возможно не станет учитывать эту особенность, даже std::auto_ptr из былых времен не хуже boost::shared_ptr

sni4ok
()
Ответ на: комментарий от sni4ok

нет никакой проблемы со скоростью, чес гря вообще пофег сколько на это время уходит, потому что задача только освободить память, а сейчас копирую в другой поток не указатель, а часть или полностью память от другой памяти.

возможно и стоит перейти на уникюптр, но это еще надо посмотреть использую ли я где то дальше эту память или после вызова она мне больше не нужна

wolverin ★★★★
() автор топика
Ответ на: комментарий от wolverin

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

sni4ok
()
Ответ на: комментарий от sni4ok

ну эт не везде получится, т.к. где то память выделена Сишным вызовом

и кстати как раз нашел у себя место почему униркюптр не подойдет - в цикле по событию «создаю» некие данные и отправляю их, т.е. здесь получается никогда не нужно чтобы память освобождалась после вызова.

wolverin ★★★★
() автор топика
Ответ на: комментарий от wolverin

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

sni4ok
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария