В настоящее время я использую функции dlopen для некоторого проекта плагина.
Этот дескриптор функции возвращает void*, а затем я сохраняю весь дескриптор на карту с именем handles:
void* handle = dlopen(path.c_str(), RTLD_LAZY);
handles[file] = handle;
Моя цель - передать право собственности на карту, я думал о unique_ptr, но не уверен, что это вообще возможно.
Если это невозможно, какие у меня есть другие альтернативы?
Возможный дубликат Почему shared_ptr<void> является допустимым, а unique_ptr<void> имеет неправильный формат?
Что именно здесь означает право собственности? IOW, какую очистку вы должны сделать, когда закончите с ручкой (если есть)?
Ручка должна быть закрыта. Поэтому я бы хотел, чтобы при очистке карты автоматически закрывались все дескрипторы (например, внутри средства удаления unique_ptr). В настоящее время мне приходится вызывать «закрыть» вручную, а также стирать, поэтому есть 2 операции.
Я не уверен, что вы действительно «владеете» пустотой*. Я имею в виду, как ты мог удалить что-то, размер которого не знаешь. Посмотрите на API, как вы должны правильно очищать дескриптор, и тогда, возможно, вы сможете обернуть это во что-то, с чем вы могли бы использовать семантику интеллектуального указателя.
@NathanCooper: да, я должен вызывать Close для каждого дескриптора, поэтому решение, возможно, состоит в том, чтобы инкапсулировать его в объект RAII и использовать его.
Я не совсем уверен, но пользовательские удаления может быть упрощенным способом достижения этого. Никогда не использовал их. Кто-то еще может ответить.





Если я правильно понимаю, вы можете сделать что-то вроде
Определите функцию закрытия и псевдоним для типа указателя:
auto closeFunc = [](void* vp) {
dlclose(vp);
};
using HandlePtr = std::unique_ptr<void, decltype(closeFunc)>;
std::map<std::string, HandlePtr> handles;
а затем создайте ручки и добавьте на карту:
void* handle = dlopen(path.c_str(), RTLD_LAZY);
HandlePtr ptr( handle, closeFunc );
handles[file] = std::move( ptr );
Затем closeFunc будет вызываться, когда уникальный ptr выйдет за пределы области видимости.
Необработанный указатель можно предотвратить, объединив две строки выше:
HandlePtr handle(dlopen(path.c_str(), RTLD_LAZY), closeFunc );
handles[file] = std::move( handle );
При этом используется второй аргумент std::unique_ptr, который указывает используемую программу удаления.
PS: maps и unique_ptrs не работают как есть, вам могут понадобиться некоторые emplaces или перемещения в зависимости от используемого вами стандарта C++. Или используйте shared_ptr вместо этого.
PS: я понятия не имею, как очищается дескриптор, так как я не знаком с dlopen, я просто предположил, что используется dlclose.
Я пробовал это, но у меня всегда выдает ошибку "использование удаленной функции" при перемещении ptr внутри карты :/
Неважно, это работает, если я добавляю данные с помощью «emplace» :)
@Adrian Возможно, отредактируйте фрагменты кода в ответе, чтобы показать emplace, как он у вас работает.
std::unique_ptr особенно полезен для «обертывания» c-библиотек, которые работают с использованием ресурса, который необходимо удалить или закрыть по завершении. Это не только мешает вам забыть, когда удалять/закрывать ресурс, но и делает использование исключения ресурса безопасным, потому что ресурс правильно очищается в случае исключения.
Я бы, наверное, сделал что-то вроде этого:
// closer function to clean up the resource
struct dl_closer{ void operator()(void* dl) const { dlclose(dl); }};
// type alias so you don't forget to include the closer functor
using unique_dl_ptr = std::unique_ptr<void, dl_closer>;
// helper function that ensures you get a valid dl handle
// or an exception.
unique_dl_ptr make_unique_dl(std::string const& file_name)
{
auto ptr = unique_dl_ptr(dlopen(file_name.c_str(), RTLD_LAZY));
if (!ptr)
throw std::runtime_error(dlerror());
return ptr;
}
И используйте его примерно так:
// open a dl
auto dl_ptr = make_unique_dl("/path/to/my/plugin/library.so");
// now set your symbols using POSIX recommended casting gymnastics
void (*my_function_name)();
if (!(*(void**)&my_function_name = dlsym(dl_ptr.get(), "my_function_name")))
throw std::runtime_error(dlerror());
А затем напишите класс, чтобы также обернуть dlsym (поэтому shared_ptr кажется более подходящим, чтобы не закрываться, пока функция все еще активна).
@Jarod42 Jarod42 Вы бы использовали Толькоstd::shared_ptr, когда ваш дизайн не позволяет узнать, какой из двух (или более) динамических объектов (которые содержат указатель на dl) первым выйдет из области видимости. Обычно std::unique_ptr является лучшим выбором, и его следует использовать, если вы не принужденный для использования std::shared_ptr (что должно быть довольно редко).
Срок жизни my_function_name должен быть короче, чем dl_ptr. При правильном использовании share_ptrmy_function_name продлит срок службы dl_ptr и, следовательно, избегает использования «висячего» my_function_name. Но на самом деле, с unique_ptr вы можете написать полную оболочку library.so, чтобы гарантировать этот срок службы.
@ Jarod42 Jarod42 Также несложно с «подходящим» использованием unique_ptr сделать то же самое - убедитесь, что ваши символы не переживут ваши dl. Вместо того, чтобы заставлять my_function_name содержать общую ссылку на ваш dl, вы можете сделать свой класс dl «обернутым» для всех функций, к которым dl предоставляет уникальный доступ.
ИМО, пользователи могут хранить функции, поэтому мне кажется естественным, что ресурс является общим. Но на самом деле, если вы не предоставляете прямой доступ к функции (или не применяете эту гарантию) unique_ptr тоже нормально. В основном зависит от интерфейса, который мы хотим для класса-оболочки.
Даже если лучше всего следовать ответу TheBadger. Помните, что в C++ нет концепции владения, C++ предоставляет классы для имитации этого и эффективного использования RAII, но на самом деле у вас нет никаких гарантий от языка, чтобы гарантировать владение ресурсом, даже с unique_ptr, например:
{
auto p_i = new int{5};
auto up_i = std::unique_ptr<int>{p_i};
*p_i = 6; //nothing in the language prevent something like this
assert(*up_i == 6); //the unique_ptr ownership is not assured as you can see here
delete p_i; //still not illegal
} //at run time, RAII mechanism strikes, destroy unique_ptr, try to free an already freed memory, ending up to a core dumped
У вас даже есть функция-член get в классе unique_ptr, которая дает вам возможность испортить вашу память, как вы могли бы это делать с необработанными указателями.
В С++ нет механизма для обнаружения нарушения «права собственности» во время компиляции, возможно, вы можете найти какие-то инструменты для его проверки, но этого нет в стандарте, это всего лишь вопрос рекомендуемых практик.
Таким образом, более уместно и точно говорить об «управляемой» памяти в C++, а не о «владении».
Мы используем этот термин, да, как способ выразить тот факт, что, вообще говоря, вы не будете изменять память, управляемую unique_ptr, и, таким образом, «право собственности» на ресурс уважается, но этот вид владения является только Что касается рекомендуемых практик, как я уже сказал, в языке нет механизма проверки во время компиляции любых нарушений прав собственности, как в rust. Я думаю, очень важно помнить об этом.
Никогда не владейте собственными указателями. Используйте ручки. Не существует разумного механизма, который предотвратит двойное освобождение собственного указателя, использование после освобождения и т.д. std::unique_ptr<HANDLE, handle_release> в один конец.
void*сам по себе не имеет понятия владения, единственное «владение», о котором знает необработанный указатель, заключается в том, что вы должны его удалить, если вы этого не сделаете, то указатель ничем не владеет.