Допустим, есть такая функция-обертка (на голом Си), в которой вы вызываете свой 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.
Так вот, браток, делаем все четко и красиво, и никаких двойных аллокаций. Память управляется нормально, код понятный, и все довольны. Дерзай! |
| |