想象一个返回必须是
free
的内容的 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
?
您所拥有的极有可能在实践中发挥作用,但并不严格正确。您可以按如下方式使其更有可能工作:
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
,但无需对被包装的对象做出假设。
最初的问题(以及 hvd 的答案)引入了每个指针的开销,因此这样的
unique_ptr
的大小是使用 std::make_unique
导出的大小的两倍。另外,我会直接制定decltype:
std::unique_ptr<char, decltype(&std::free)>
t_copy { strdup(t), &std::free };
如果拥有许多 C-API 派生指针,那么额外的空间可能会成为一种负担,阻碍采用安全的 C++ RAII 来包装需要
free()
d 的现有 POSIX 风格 API 的 C++ 代码。可能出现的另一个问题是,当您在上述情况下使用 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) };
我建议应该在 C++ 核心指南支持库中提供该机制(也许有更好的命名),这样它就可以用于过渡到现代 C++ 的代码库(成功打开)。
假设这是有道理的,可以使用类似的模式 非指针?例如 POSIX 的 open 函数返回 一个整数?
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()
在失败时返回的值。将此代码模板化也很容易,以便它接受其他句柄类型和/或无效值。
然后
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();
}
演示。
只是为了更新答案:C++20 允许在未计算的上下文中使用 lambda(如
decltype
),因此不再需要编写一个结构来调用 std::free
来利用空基优化。下面就可以了。
std::unique_ptr<T, decltype([](void *p) { std::free(p); })> ptr;
技巧在于这样的 lambda 是默认可构造的。
假设这是有道理的,可以使用类似的模式 非指针?例如 POSIX 的 open 函数返回 一个整数?
当然,使用 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 };
}
或者,您可以使用函子代替 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 和“工厂”函数,则可以进一步简化示例。
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);
}
另一种方法是使用
std::integral_constant
将 free
包装为模板参数的一部分:
std::unique_ptr<char, std::integral_constant<decltype(free)*, free>>
这种方法的一大优点是,每次创建智能指针时,您不再需要提供
free
作为对象构造函数的参数。
您可以通过定义围绕它的类型来使事物更具可读性:
using ScopedPtrFree = std::unique_ptr<char, std::integral_constant<decltype(free)*, free>>;
或者更通用:
template<typename T>
using ScopedPtrFree = std::unique_ptr<T, std::integral_constant<decltype(free)*, free>>;
然后简单地像这样使用它:
ScopedPtrFree<char> ptr(strdup(t));