std::unique_ptr 用于需要 free 的 C 函数

问题描述 投票:0回答:6

想象一个返回必须是

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

c++ c++11
6个回答
70
投票

您所拥有的极有可能在实践中发挥作用,但并不严格正确。您可以按如下方式使其更有可能工作:

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
,但无需对被包装的对象做出假设。


47
投票

最初的问题(以及 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++ 的代码库(成功打开)。


8
投票

假设这是有道理的,可以使用类似的模式 非指针?例如 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();
}

演示


7
投票

只是为了更新答案:C++20 允许在未计算的上下文中使用 lambda(如

decltype
),因此不再需要编写一个结构来调用
std::free
来利用空基优化。下面就可以了。

std::unique_ptr<T, decltype([](void *p) { std::free(p); })> ptr;

技巧在于这样的 lambda 是默认可构造的。


6
投票

假设这是有道理的,可以使用类似的模式 非指针?例如 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);
}

0
投票

另一种方法是使用

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));
© www.soinside.com 2019 - 2024. All rights reserved.