std :: unique_ptr,用于需要空闲的C函数

思考一个C函数返回的东西必须是free ,例如POSIX的strdup() 。 我想在C ++ 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

你有什么可能在实践中工作,但不是严格正确的。 然而,正确的版本可以说是更可读:

 std::unique_ptr<char, decltype(std::free) *> t_copy { strdup(t), std::free }; 

原因是std::free的函数types不保证是void(void*) 。 它保证带一个void* ,并返回void ,但有两个函数types符合该规范:一个是C连接,一个是C ++连接。 大多数编译器都没有注意到这一点,但为了正确,你应该避免对此做出假设。

假设它是有道理的,有可能使用非指针类似的模式?

不与unique_ptr ,这是真正特定于指针。 但是你可以创build自己的类,类似于unique_ptr ,但是不需要假定被包装的对象。

原来的问题(和hvd的答案 )引入了每个指针的开销,所以这样一个unique_ptr的大小是用std::make_unique派生的大小的两倍。 另外,我会直接制定decltype:

 std::unique_ptr<char, decltype(&std::free)> t_copy { strdup(t), &std::free }; 

如果有许多这些C-API派生的指针,那么额外的空间可能成为阻碍采用安全的C ++ RAII来包装现有的POSIX风格的API的C ++代码的负担。 另一个可能出现的问题是,当你在上面的情况下使用char const时候,你会得到一个编译错误,因为你不能自动将char const *转换为free(void *)的Parametertypes。

我build议使用专门的删除types,而不是一个build立在飞行中,以便空间开销消失,所需的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) }; 

我会build议应该在C ++ Core Guidelines支持库中提供这个机制(可能有一个更好的命名),所以它可以用于过渡到现代C ++(成功开放)的代码库。

假设它是有道理的,有可能使用非指针类似的模式? 例如对于POSIX的函数打开,返回一个int?

当然, 在unique_ptr上使用Howard的Hinnant 教程 ,我们可以看到一个激励的例子:

 // 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 }; } 

或者,你可以使用函数来代替lambda:

 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和“工厂”function,这个例子可以进一步减less。

 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); } 

假设它是有道理的,有可能使用非指针类似的模式? 例如对于POSIX的函数打开,返回一个int?

是的,可以做到。 您需要一个满足NullablePointer要求的“指针”types:

 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()在失败时返回的值。 对代码进行模板化也很容易,以便接受其他句柄types和/或无效值。

然后

 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(); } 

演示 。