std :: unique_ptr用于需要免费的C函数

Pao*_*oni 50 c++ c++11

想想一个返回必须为freed的东西的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;
}
Run Code Online (Sandbox Code Playgroud)

假设它有意义,可以使用与非指针相似的模式吗?例如,POSIX的函数open返回int

小智 60

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

std::unique_ptr<char, decltype(std::free) *>
    t_copy { strdup(t), std::free };
Run Code Online (Sandbox Code Playgroud)

原因是std::free不保证函数类型void(void*).它保证在传递a时是可调用的void*,并且在这种情况下返回void,但是至少有两个与该规范匹配的函数类型:一个具有C链接,一个具有C++链接.大多数编译器都不关注它,但为了正确,你应该避免对它做出假设.

然而,即使这样,这也不是严格正确的.正如@PeterSom在评论中指出的那样,C++允许实现例如创建std::free一个重载函数,在这种情况下,你和我的使用std::free都是模棱两可的.这不是授予的特定权限std::free,它是几乎任何标准库函数的权限.要避免此问题,需要自定义函数或仿函数(如在他的答案中).

假设它有意义,可以使用与非指针相似的模式吗?

不是unique_ptr,这是指针真正具体.但是你可以创建自己的类,类似于unique_ptr,但不会对被包装的对象做出假设.

  • @ Paolo.Bolzoni:是的,它们是不同的类型,你不能(便携地)使用相同的指针类型. (4认同)
  • @ Paolo.Bolzoni一些实现真的抱怨`extern"C"{void f(); } void(*fp)()= f;`带有关于从不兼容的指针类型赋值的消息.我想我记得曾经读过惠普的一个实现,它会拒绝它,但是我没办法验证它.Sun(现在是Oracle)是另一个关注它的实现,但它允许这种隐含的转换带有警告(我已经看到了自己). (3认同)

Pet*_*Som 29

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

std::unique_ptr<char, decltype(&std::free)>
    t_copy { strdup(t), &std::free };
Run Code Online (Sandbox Code Playgroud)

如果有许多这些C-API派生指针,额外的空间可能会成为阻碍采用安全C++ RAII的C++代码包装现有POSIX样式API的负担free().可能出现的另一个问题是,当您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
Run Code Online (Sandbox Code Playgroud)

现在的例子变成了

unique_C_ptr<char const> t_copy { strdup(t) };
Run Code Online (Sandbox Code Playgroud)

我将建议该机制应该在C++核心指南支持库中提供(可能具有更好的命名),因此它可以用于转换到现代C++(成功打开)的代码库.


T.C*_*.C. 10

假设它有意义,可以使用与非指针相似的模式吗?例如,对于POSIX的函数open,返回一个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;
};
Run Code Online (Sandbox Code Playgroud)

请注意,我们在这里使用-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();
}
Run Code Online (Sandbox Code Playgroud)

演示.


小智 7

假设它有意义,可以使用与非指针相似的模式吗?例如,对于POSIX的函数open,返回一个int?

当然,在unique_ptr上使用霍华德的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 };
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用函子而不是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 };
}
Run Code Online (Sandbox Code Playgroud)

如果我们使用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);
}
Run Code Online (Sandbox Code Playgroud)

  • 既然你现在存储了一个指向句柄的指针,而不是句柄本身,如果在`unique_ptr`之前销毁`handle`,那么对象的生命周期可能会出现问题.你试图通过使用`new handle`来解决这个问题,但在这种情况下,不要忘记`close_handler`不仅需要关闭句柄,还需要释放内存,否则你就会泄漏. (7认同)
  • 换句话说,`operator()`需要`delete handle`才能正常工作. (2认同)

Hun*_*ler 5

只是为了更新答案:C++20 允许在未评估的上下文中使用 lambda(例如decltype),因此不再需要编写一个结构来调用std::free以利用空基优化。下面就可以了。

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

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