使用std::make_unique与自定义删除器

55

在使用自定义删除器时,我希望使用std::make_unique而不是一个原始的new。 我正在使用VC++2013。 在我看来,如果您正在使用自定义删除器,则没有办法使用std::unique_ptr。 我错过了什么还是确实如此?


其他信息:

我正在使用std::unique_ptr<HANDLE,custom_deleter>来保存打开的COM端口的Windows HANDLE。

我可以为此编写一个自定义RAII类,这并不难,但我想看看使用std::unique_ptr有多难/困难/糟糕。


是的,这似乎是“make_unique”中的一个缺陷。 - user3286380
3
使用场景是什么?如果你正在使用 std::make_unique,它会使用 new 进行内存分配,因此默认的删除器(使用 delete)是正确匹配的删除器。 - James McNellis
3个回答

44
make_unique 的整个意义在于封装“使用给定的构造函数参数使用 new 创建 T 并使用 delete 销毁它”的概念。

如果您想要一个自定义删除器,您还必须指定如何创建对象,然后就不会从 emplacing 制造函数中获得任何更多的收益。

我在这篇文章中为某些独特的资源句柄编写了一些自定义制造函数的示例。


4
理论上来说,可以指定一个“创建者”和一个“删除者”,这样你就能获得异常安全的好处。 - user3286380
1
@LucDanton:是和不是。allocate_shared只使用用户定义的分配器,而不是删除器。这个概念的功能显然较弱。例如,你不能用它包装一个std::FILE*。换句话说,allocate_shared并没有暴露出std::shared_ptr的全部能力,它只允许你自定义内存分配器。 - Kerrek SB
1
呃!在我看来,他们定义make_unique的方式几乎没有用处。相比于直接使用unique_ptr构造函数,它到底带给我们什么好处?我真正想要的是一个接受删除器作为参数的make_unique变体,这样它的类型就可以被推导出来。这将让我创建具有不可言喻的删除器类型的unique_ptrs成为可能。例如:auto p = std::make_unique<Foo>(x, y, z, [] (Foo* obj) { FancyFreeFunction(obj); }); - Peter Ruderman
这是一个很好的观点。我猜这让我们陷入了整个“allocate_unique”的争论中。在我上面即兴举的例子中,唯一的方法就是使用operator new。 - Peter Ruderman
@PeterRuderman:分配和删除是有些不同的事情。想想 fopen/fclose。如果你只想要一个普通的唯一指针,它使用从分配器获得的内存,那么你可以拥有一个 allocator_deleter,但这只解决了问题域的一部分。 - Kerrek SB
2
有一个非常普遍的情况可以从带有删除器的make_unique中受益,这是处理unique_ptrs内不完整类型的情况,其中您有一个仅调用delete的删除器,但其operator()在具有完整类型的TU中定义。 - etarion

3

以下是一种将C风格内存管理包装到使用自定义删除器调用自定义释放函数的std::unique_ptr中的方法。它具有类似于std::make_unique的make函数助手 LIVE:

#include <iostream>
#include <functional>
#include <memory>

// Some C style code that has some custom free function ptr...
extern "C" {

struct ABC { };

enum free_type_e {
    FREE_ALL,
    FREE_SOME
};

typedef void (free_f)(enum free_type_e free_type, void *ptr);
struct some_c_ops { free_f* free_op; };

void MY_free(enum free_type_e free_type, void *ptr)
{
    printf("%s:%d ptr=%ld\n", __func__, __LINE__, (long)ptr);
    (void)free_type;
    free(ptr);
}

}; // extern "C"

template<typename T>
using c_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

template <typename T>
c_unique_ptr<T> make_c_unique(some_c_ops* op, free_type_e free_type)
{
    return c_unique_ptr<T>(static_cast<T*>(calloc(1, sizeof(T))),
                           std::bind(op->free_op, free_type, std::placeholders::_1));
}

void foo(c_unique_ptr<ABC> ptr)
{
    std::cout << __func__ << ":" << __LINE__
        << " ptr=" << reinterpret_cast<size_t>(ptr.get()) <<     std::endl;
}

int main()
{
    some_c_ops ops = { MY_free };
    c_unique_ptr<ABC> ptr = make_c_unique<ABC>(&ops, FREE_ALL);
    std::cout << __func__ << ":" << __LINE__
        << " ptr=" << reinterpret_cast<size_t>(ptr.get()) << std::endl;

    foo(std::move(ptr));

    std::cout << __func__ << ":" << __LINE__
        << " ptr=" << reinterpret_cast<size_t>(ptr.get()) << std::endl;
}

可能的输出:

main:48 ptr=50511440
foo:40 ptr=50511440
MY_free:20 ptr=50511440
main:53 ptr=0

我喜欢自定义别名 - 这绝对使事情变得更容易。然后,我像这样创建我的 unique_ptr 自定义指针包装器 - 定义:class MyClass { private: c_unique_ptr<ExternalType> my_wrapper; },然后初始化:my_wrapper = { externalCreatorFunc(args), [](auto p) { externalReleaserFunc(p); } }; - Guss

-3
据我所知,C++11标准中没有make_unique函数。请参见: 因此,我认为make_unique是微软的一种实现,至少不包含在标准中。
但是,您仍然可以使用自定义删除器与unique_ptr。当使用unique_ptr时,您必须将删除器的类型作为第二个模板参数指定,然后将适当的对象传递给构造函数。

5
C++14将添加std::make_unique。不幸的是,在std::make_unique中没有指定delete的方法,因此它只能返回一个具有默认删除器的unique pointer。 - Graznarak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接