如何将deleter传递给make_shared函数?

53

自从C++11以来,由于多种原因,开发者倾向于使用智能指针类来管理动态分配的对象的生命周期。随着这些新的智能指针类的出现,标准甚至建议不要使用像new这样的运算符,而是建议使用make_sharedmake_unique来避免一些容易出错的问题。

如果我们想使用一个智能指针类,比如shared_ptr,我们可以像这样构造一个:

shared_ptr<int> p(new int(12));

此外,我们希望将自定义删除器传递给智能指针类。

shared_ptr<int> p(new int(12), deleter);

另一方面,如果我们想使用make_shared来分配内存,例如int,而不是使用newshared_ptr构造函数,就像上面第一个表达式那样,我们可以使用:

std::make_shared<int>();
auto ip = make_shared<int>(12);

但是如果我们还想将自定义的删除器传递给 make_shared ,有正确的方法吗? 似乎编译器(至少是gcc)会报错:

auto ip = make_shared<int>(12, deleter);

编写自己的 make_shared() 函数以支持此操作,这是可行的。 - user1095108
5个回答

82
正如其他人所说,make_shared不能与自定义删除器一起使用。但我想解释一下为什么。
自定义删除器存在的原因是您以某种特殊的方式分配了指针,因此您需要能够以相应的特殊方式释放它。而make_shared使用new来分配指针。使用new分配的对象应该使用delete来释放。标准删除器会如实地执行这个操作。
简而言之,如果您可以接受默认的分配行为,则也可以接受默认的释放行为。如果您无法接受默认的分配行为,则应使用allocate_shared。它使用提供的分配器同时分配和释放存储空间。
此外,make_shared允许(并且几乎肯定会)在同一个分配中分配T的内存和shared_ptr的控制块。这是您的删除器无法了解或处理的内容。而allocate_shared能够处理它,因为您提供的分配器可以执行分配和释放职责。

3
点赞,我从未想过,但它非常合理。好提示。 - skypjack
2
底线是,因为make/allocate_shared不允许您更改“获取资源”步骤,所以它们不支持自定义的“释放资源”方式也就没有意义。它们用于简单的创建/销毁对象使用shared_ptr - Jonathan Wakely
@JonathanWakely:所以我应该说,“简而言之,如果你可以接受默认的分配行为,那么你也可以接受默认的释放行为?” 我使用allocate_shared的观点是它可以在许多需要显式删除器的情况下使用。并非全部情况,但很多情况都可以。 - Nicol Bolas
正如我所说,“取消创建”不仅涉及到释放内存,还包括析构函数。但通常情况下,当您需要自定义删除器时,并不一定涉及任何析构函数或内存释放。 - Jonathan Wakely
1
“make_shared”会使用“new”来分配指针。使用“new”分配的对象应该使用“delete”进行释放,这也是标准删除器的职责所在。但是,“make_shared”并不保证一定使用“new”,它也可以使用“std::allocator”(就像我的实现方式一样),或者“malloc”,或者自定义分配器等其他方式。你无从得知,因此你的删除器也不知道该怎么做。既然“make_shared”创建了对象,那么你应该相信它能够正确地销毁它。 - Jonathan Wakely
显示剩余5条评论

13
根据文档make_shared 接受一个参数列表来构造 T 类的实例。
此外,文档还指出:

通常使用该函数替代通过 new 调用返回的原始指针构造 std::shared_ptr(new T(args...)) 的方法。

因此,可以推断出无法设置自定义的 删除器
要执行此操作,需要使用正确的构造函数手动创建 shared_ptr
作为所提供的构造函数列表中的一个示例,可以使用以下方式:

template< class Y, class Deleter > 
shared_ptr( Y* ptr, Deleter d );

因此,代码将类似于:
auto ptr = std::shared_ptr(new MyClass{arg1, arg2}, myDeleter);

改为:

auto ptr = std::make_shared<MyClass>(arg1, arg2);

5

未指定make_shared如何获取对象的内存(可能使用operator newmalloc或某种分配器),因此自定义删除器无法知道如何正确处理。 make_shared创建对象,因此您还必须依赖它正确地销毁对象并进行适当的清理。

此外,我们想将自定义删除器传递给智能指针类,

shared_ptr<int> p(new int(12), deleter);

我认为这不是一个非常现实的例子。自定义删除器通常在以某种特殊方式获取资源时使用。如果您只是像这样使用new创建它,那么为什么需要自定义删除器呢?

如果您只想在析构时运行一些代码,请将其放入析构函数中!这样您也可以继续使用make_shared

struct RunSomethingOnDestruction {
  RunSomethingOnDestruction(int n) : i(n) { }
  ~RunSomethingOnDestruction() { /* something */ }
  int i;
};

auto px = std::make_shared<RunSomethingOnDestruction>(12);
std:shared_ptr<int> p(px, px->i);

这将为您提供一个由make_shared创建的shared_ptr<int>(因此您会得到make_shared完成的内存优化),它将在销毁时运行一些自定义代码。

如果您无法更改所指向的对象的析构函数呢? - Spongman
然后使用自定义删除器,不要使用 make_shared - Jonathan Wakely

4
你不能。 make_shared<T> 会将提供的参数转发给类型为T的构造函数。它用于简单情况,当你想要默认删除器时。

0

如果您使用自定义删除器,创建智能指针对象时就不能使用make_uniquemake_shared函数。由于我们需要提供自定义删除器,这些函数不支持该操作。

如果需要自定义删除器或从其他地方采用原始指针,请勿使用make_unique或make_shared。

这个想法是,如果您需要一种专门的方法来删除对象,那么您可能也需要一种专门的方法来创建它们。

假设我们有一个名为Test的类。

#include <iostream>    
using namespace std;    
class Test
{
private : 
    int data; 
    public : 
    Test() :data{0}
    {
        cout << "Test constructor (" << data << ")" << endl;
    }
    Test(int d) : data{ d }
    {
        cout << "Test constructor (" << data << ")" << endl; 
    }
    int get_data() const { return data; }
    ~Test()
    { 
        cout << "Test Destructor (" << data << ')' << endl; 
    }
};

// main function. 
int main()
{
   // It's fine if you use  make_shared and custom deleter like this
   std::shared_ptr<Test> ptr(new Test{1000},
            [](Test *ptr)
            {
                cout << "some Code that you want to execute "; 
                delete ptr;
            });
         return 0;
}

但是如果你使用make_shared函数,你会得到一个编译器错误

std::shared_ptr<Test> ptr = make_shared<Test>(1000,
            [](Test *ptr){
               cout << "some Code that you want to execute "; 
               delete ptr;
            });

基本上,make_shared函数是对newdelete的封装,如果你想要自定义删除器,就必须提供自己的newdelete

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