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

时间:2014-12-12 09:51:59

标签: c++ c++11

想想一个返回必须为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

4 个答案:

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

Demo

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