Про переход между C++ и FFI (голый C) и умные указатели
От: Shmj Ниоткуда  
Дата: 02.08.24 04:33
Оценка: -1
Допустим, есть такая функция-обертка (на голом Си), в которой вы вызываете свой C++ -код.

#ifdef __cplusplus
extern "C" {
#endif

void fun1(char **out_value)
{
   // Тут вызываете свой нормальный C++ -код и устанавливаете *out_value
}


#ifdef __cplusplus
}
#endif


Часто вижу — люди делают strdup, чтобы создать копию std::string .c_str(). Т.е. каждый раз выделяют память по 2 раза — на каждый чих.

Проблема в том что ни std::string ни vector — простым способом не хотят отдавать владение над своими данными. Но тогда какие есть варианты, чтобы память не выделять по 2 раза? Варианты:

1. Можно использовать сырые указатели и ручное управление. Но хотелось бы чтобы C++ код был все-таки более-менее на умных указателях, а уже внешние C-обертки работали с сырыми.
2. Можно использовать unique_ptr<char[]>. Тогда если вызывать из голого C|FFI — то можно сделать .release — и получить сырой указатель. А внутри вы сможете работать с unique_ptr. Правда shared_ptr уже не получится простым способом использовать для подобных целей.

Так же тут стоит вопрос очистки памяти — некоторые либы хотят очищать своими метдами, т.к. через free сишный, а не через вызов функций очистки вашей библиотеки. Т.е. вы могли выделить с помощью new и вернуть — а они будут очищать сами с помощью free, что вроде как иногда может вызывать проблемы (но не уверен на счет char* ).

Возможно ли без копирования и аллокаций двойных?

З.Ы. Сразу дают ответ GPT. За одно многие здесь не очень понимают суть вопроса — а GPT понимает лучше людей смысл вопроса, по этому из его ответа сможете понять вопрос лучше:

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

Значит так, сишная обертка у нас есть, да? Надо только сделать так, чтобы наш C++ код отдавал память четко и красиво, чтобы потом ее можно было нормально чистить, а не бегать с этим free и new как ужаленный.

Вариант с unique_ptr<char[]> вполне норм, если правильно заюзать. Давай замутим так, чтобы мы могли этот указатель спокойно передать наружу, а память чистилась потом четко.

Наши шаги

В C++ коде работаем с std::string или чем-то подобным.

Используем unique_ptr<char[]>, чтобы передавать память наружу.

Для C-шного кода делаем функцию для освобождения памяти.

Короче, гоним код:


#ifdef __cplusplus
extern "C" {
#endif

#include <cstdlib> // Для free в C

void fun1(char **out_value)
{
    if (!out_value) return;

    // Тут вызываем C++ код
    std::string result = my_cpp_function(); // допустим, эта функция возвращает std::string

    // Делаем unique_ptr<char[]> и освобождаем его через .release()
    std::unique_ptr<char[]> unique_result(new char[result.size() + 1]);
    std::copy(result.begin(), result.end(), unique_result.get());
    unique_result[result.size()] = '\0'; // не забываем про нулевой символ

    // Передаем сырой указатель наружу
    *out_value = unique_result.release();
}

void free_memory(char *ptr)
{
    free(ptr); // Это для тех, кто хочет чистить память через free
}

#ifdef __cplusplus
}
#endif


Что тут по итогу:

std::string: мы спокойно работаем с этим типом в C++ коде.
unique_ptr<char[]>: используем для управления памятью, которая передается наружу.
release(): забирает управление у unique_ptr, и мы можем передать сырой указатель в C-код.
free_memory: отдельная функция для освобождения памяти, которая может быть вызвана из C-кода, если там вдруг захотят чистить память через free.

Так вот, браток, делаем все четко и красиво, и никаких двойных аллокаций. Память управляется нормально, код понятный, и все довольны. Дерзай!
Отредактировано 02.08.2024 4:33 Shmj . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.