Std:: unique_ptr для функций C, которые нужны бесплатно
Подумайте о функции C, возвращающей то, что должно быть free
d, например POSIX strdup()
. Я хочу использовать эту функцию в С++ 11 и избегать любых утечек, это правильный способ?
#include <memory>
#include <iostream>
#include <string.h>
int main() {
char const* t { "Hi stackoverflow!" };
std::unique_ptr<char, void(*)(void*)>
t_copy { strdup(t), std::free };
std::cout << t_copy.get() << " <- this is the copy!" <<std::endl;
}
Предполагая, что это имеет смысл, можно использовать аналогичный шаблон с не указателями? Например, для функции POSIX open
, которая возвращает int
?
Ответы
Ответ 1
То, что у вас есть, с большой вероятностью будет работать на практике, но не строго корректно. Однако правильная версия, возможно, еще более читаема:
std::unique_ptr<char, decltype(std::free) *>
t_copy { strdup(t), std::free };
Причина в том, что тип функции std::free
не гарантируется void(void*)
. Гарантируется принятие void*
и возврат void
, но есть два типа функций, которые соответствуют этой спецификации: одна с C-связью и одна с С++-связью. Большинство компиляторов не обращают на это внимания, но для правильности вам следует избегать предположений об этом.
Предполагая, что это имеет смысл, можно использовать аналогичный шаблон с не указателями?
Не с unique_ptr
, что действительно специфично для указателей. Но вы можете создать свой собственный класс, похожий на unique_ptr
, но без предположений об обертываемом объекте.
Ответ 2
В исходном вопросе (и hvd answer) вводятся служебные данные с указателем, поэтому такой unique_ptr
в два раза больше, чем один, полученный с помощью std::make_unique
. Кроме того, я бы сразу сформулировал decltype:
std::unique_ptr<char, decltype(&std::free)>
t_copy { strdup(t), &std::free };
Если у многих из этих указателей, основанных на C-API, дополнительное пространство может стать бременем, препятствующим принятию безопасного кода С++ RAII для кода С++, обертывающего существующие API-интерфейсы типа POSIX, требующие быть free()
d. Другая проблема, которая может возникнуть, заключается в том, что при использовании char const
в приведенной выше ситуации вы получаете ошибку компиляции, потому что вы не можете автоматически преобразовать char const *
в тип параметра free(void *)
.
Я предлагаю использовать выделенный тип делетера, а не встроенный на лету, так что пространство над головой уходит, а требуемый const_cast
также не является проблемой. Затем псевдоним шаблона можно легко использовать для переноса указателей на C-API:
struct free_deleter{
template <typename T>
void operator()(T *p) const {
std::free(const_cast<std::remove_const_t<T>*>(p));
}
};
template <typename T>
using unique_C_ptr=std::unique_ptr<T,free_deleter>;
static_assert(sizeof(char *)==
sizeof(unique_C_ptr<char>),""); // ensure no overhead
Теперь пример станет
unique_C_ptr<char const> t_copy { strdup(t) };
Я предлагаю, чтобы этот механизм был доступен в библиотеке поддержки основных принципов С++ (возможно, с лучшим наименованием), поэтому он может быть доступен для баз кода, переходящих на современный С++ (успех открыт).
Ответ 3
Предполагая, что это имеет смысл, можно использовать аналогичную модель с не-указатели? Например, для открытия функции POSIX, которая возвращает int?
Конечно, используя Howard Hinnant учебник по unique_ptr, мы можем увидеть мотивирующий пример:
// For open
#include <sys/stat.h>
#include <fcntl.h>
// For close
#include <unistd.h>
// For unique_ptr
#include <memory>
int main()
{
auto handle_deleter = [] (int* handle) {
close(*handle);
};
int handle = open("main.cpp", O_RDONLY);
std::unique_ptr<int, decltype(handle_deleter)> uptr
{ &handle, handle_deleter };
}
В качестве альтернативы вы можете использовать функтор вместо лямбда:
struct close_handler
{
void operator()(int* handle) const
{
close(*handle);
}
};
int main()
{
int handle = open("main.cpp", O_RDONLY);
std::unique_ptr<int, close_handler> uptr
{ &handle };
}
Пример может быть дополнительно уменьшен, если мы используем функцию typedef и "factory".
using handle = int;
using handle_ptr = std::unique_ptr<handle, close_handler>;
template <typename... T>
handle_ptr get_file_handle(T&&... args)
{
return handle_ptr(new handle{open(std::forward<T>(args)...)});
}
int main()
{
handle_ptr hp = get_file_handle("main.cpp", O_RDONLY);
}
Ответ 4
Предполагая, что это имеет смысл, можно использовать аналогичную модель с не-указатели? Например, для открытия функции POSIX, которая возвращает int?
Да, это можно сделать. Вам нужен тип "указатель", который удовлетворяет требованиям NullablePointer
:
struct handle_wrapper {
handle_wrapper() noexcept : handle(-1) {}
explicit handle_wrapper(int h) noexcept : handle(h) {}
handle_wrapper(std::nullptr_t) noexcept : handle_wrapper() {}
int operator *() const noexcept { return handle; }
explicit operator bool() const noexcept { return *this != nullptr; }
friend bool operator!=(const handle_wrapper& a, const handle_wrapper& b) noexcept {
return a.handle != b.handle;
}
friend bool operator==(const handle_wrapper& a, const handle_wrapper& b) noexcept {
return a.handle == b.handle;
}
int handle;
};
Обратите внимание, что здесь мы используем -1 как значение нулевого дескриптора, потому что то, что open()
возвращается при ошибке. Также очень легко templatize этот код, чтобы он принимал другие типы дескрипторов и/или недопустимые значения.
Тогда
struct posix_close
{
using pointer = handle_wrapper;
void operator()(pointer fd) const
{
close(*fd);
}
};
int
main()
{
std::unique_ptr<int, posix_close> p(handle_wrapper(open("testing", O_CREAT)));
int fd = *p.get();
}
Демо.