想想一个返回必须为free
d的东西的C函数,例如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
?
答案 0 :(得分:58)
你所拥有的东西极有可能在实践中发挥作用,但并非严格正确。您可以更有可能按以下方式工作:
std::unique_ptr<char, decltype(std::free) *>
t_copy { strdup(t), std::free };
原因是std::free
的函数类型不能保证为void(void*)
。保证在传递void*
时可调用,并且在这种情况下返回void
,但至少有两个与该规范匹配的函数类型:一个具有C链接,另一个具有C ++链接。大多数编译器都不关注它,但为了正确,你应该避免对它做出假设。
然而,即使这样,这也不是严格正确的。正如@PeterSom在评论中指出的那样,C ++允许实现例如使std::free
成为一个重载函数,在这种情况下,你和我对std::free
的使用都是模棱两可的。这不是为std::free
授予的特定权限,它是几乎任何标准库函数的权限。为了避免这个问题,需要一个自定义函数或仿函数(如他的答案)。
假设它有意义,可以使用与非指针相似的模式吗?
不是unique_ptr
,这是指针特有的。但是你可以创建自己的类,类似于unique_ptr
,但不会对被包裹的对象做出假设。
答案 1 :(得分:27)
原始问题(和hvd's answer)引入了每指针开销,因此unique_ptr
的大小是std::make_unique
的两倍。另外,我会直接制定decltype:
std::unique_ptr<char, decltype(&std::free)>
t_copy { strdup(t), &std::free };
如果有许多这些C-API派生指针,额外空间可能成为阻碍采用安全C ++ RAII的C ++代码包装现有POSIX样式API需要free()
d的负担。可能出现的另一个问题是,在上述情况下使用char const
时,会出现编译错误,因为您无法自动将char const *
转换为free(void *)
的参数类型。 / p>
我建议使用专用的删除器类型,而不是动态构建的删除器类型,这样空间开销就会消失,所需的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) };
我建议应该在C ++核心指南支持库中提供该机制(可能有更好的命名),因此它可以用于转换到现代C ++的代码库(成功打开)。
答案 2 :(得分:9)
假设它有意义,可以使用类似的模式 非球?例如,POSIX的函数open返回 一个int?
是的,可以做到。您需要一个满足the NullablePointer
requirements的“指针”类型:
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()
失败时返回的内容。这个代码的模板化也很容易,因此它接受其他句柄类型和/或无效值。
然后
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();
}
答案 3 :(得分:7)
假设它有意义,可以使用类似的模式 非球?例如,POSIX的函数open返回 一个int?
当然,使用霍华德的Hinnant tutorial on 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 };
}
或者你可以使用函子而不是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和&#34;工厂&#34;那么可以进一步减少这个例子。功能
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);
}