使用带有自定义释放器的 std::unique_ptr 来包装原始指针

Posted

技术标签:

【中文标题】使用带有自定义释放器的 std::unique_ptr 来包装原始指针【英文标题】:Using std::unique_ptr with a custom deallocator for wrapping raw pointers 【发布时间】:2012-06-26 23:13:00 【问题描述】:

我正在尝试将libsvm 用于某个复杂的应用程序,并且由于 libsvm 主要是一个 C 库,因此在加载某些数据后必须使用自定义 API 函数来释放内存。这就是我的意思:

struct svm_model *model;
model = svm_load_model("path to model file");

//do some processing

svm_free_and_destroy_model(&this->model);

这些是我使用的 libsvm API 函数的定义:

struct svm_model *svm_load_model(const char *model_file_name);
void svm_free_and_destroy_model(struct svm_model **model_ptr_ptr);

虽然这工作得很好,但如果在我处理模型数据时发生异常,那么我最终会出现内存泄漏。为了防止这种情况,我将上面的代码封装在一个类中,在构造函数中调用svm_load_model,在析构函数中调用svm_free_and_destroy_model

现在,由于我们处于智能指针的时代,我想变得更有创意,并且,不知何故,将模型变量声明为std::unique_ptr,将指针设置为svm_free_and_destroy_model 作为自定义解除分配器,但不幸的是,我无法弄清楚这样的事情是否可行。目前,我什至无法编译它,我只是在黑暗中拍摄。以下是我认为它应该如何工作:

std::unique_ptr<struct svm_model *, /* what should I add here? */ > model (svm_load_model("path to model file"), svm_free_and_destroy_model);

【问题讨论】:

我理解您使用 C++ 中所有出色功能的动机,但您之前的解决方案确实没有任何问题。有时简单的答案是最好的。 @Mark Ransom:他之前的解决方案的问题是模型在遇到异常时会泄漏。没有用 try... catch(...) if(model) free(model); 包装所有代码扔; 如果(模型)免费(模型); @K-ballo,我指的解决方案是这个,它没有泄漏问题,因为它本质上是一个自制的智能指针:“为了防止这种情况,我包装了上面的代码在一个类中,我在构造函数中调用 svm_load_model,在析构函数中调用 svm_free_and_destroy_model。" @K-ballo 不一定; OP 说他已经为 libsvm 调用创建了一个 RAII 样式的包装器,它应该可以防止泄漏。就个人而言,我发现这种方法在使用 C API 时更有用,因为这样您就可以为所有模型处理调用创建转发函数作为 RAII 包装器中的成员函数。 另外,我想了解如何在实践中使用 C++ 的高级特性。阅读示例很好,但是当您真正尝试实现它们时,它会变得非常非常困难...... 【参考方案1】:

std::unique_ptr 的类型参数必须是 T,而不是 T *。使用 lambda 调用删除函数。

std::unique_ptr<svm_model, void(*)(svm_model *)> 
  p( svm_load_model( "path_to_model" ), 
     []( svm_model *mdl )  
       svm_free_and_detroy_model( &mdl ); 
      
   );

由于 VS2010 没有实现无状态 lambda 到函数指针的转换,您必须使用 std::function 才能使其工作。

std::unique_ptr<svm_model, std::function<void(svm_model*)>>
  p( svm_load_model("path_to_model"), 
     []( svm_model *mdl ) 
       svm_free_and_destroy_model( &mdl );
     
   );

【讨论】:

这是一个非常有趣的方法,但是我收到关于构造函数的第一个参数的错误:error C2664: 'std::unique_ptr&lt;_Ty,_Dx&gt;::unique_ptr(svm_model *,void (__cdecl *const &amp;)(svm_model *&amp;))' : cannot convert parameter 1 from 'svm_model *' to 'svm_model *' @MihaiTodor 当我在所有函数调用中使用struct svm_model 时,我遇到了类似的错误,但是通过删除struct 能够摆脱它们……不知道为什么。此外,我发布的解决方案在 VS2010 中不起作用,因为它没有实现无状态 lambda 到函数指针的转换。我将添加另一个应该也可以工作的解决方案。 我认为你在这里犯了一个错误[]( svm_load_model *mdl ),应该是[]( svm_model *mdl ),但它有效,也很有意义。不过,我还有一个问题:如果 svm_load_model 返回 NULL 会发生什么?我该如何测试呢? @MihaiTodor 哎呀,复制粘贴错误,已修复。您可以在构造unique_ptr后使用if(!p) throw something; 检查nullptr @MihaiTodor 成员变量p 的类型将是std::unique_ptr&lt;svm_model, void(*)(svm_model *)&gt;(或std::function 变体)。然后,您可以使用对svm_load_model 的调用并提供删除器lambda 在构造函数的初始化列表中对其进行初始化。默认情况下,unique_ptr 将内部指针初始化为nullptr,但我不确定自定义删除器的情况,但 VS2010 编译成功。稍后要对其进行初始化,您必须创建一个相同类型的临时unique_ptr 并将std::move 它放入p【参考方案2】:

unique_ptrdeleter 应该是一个只接受一个指针的函数,而不是一个指向指针的指针。你可以试试:

void svm_deleter(svm_model*& model)

    svm_free_and_detroy_model(&model);


....

std::unique_ptr< svm_model, void(*)(svm_model*&) >(
    svm_load_model("path to model file")
  , &svm_deleter
);

【讨论】:

我收到关于构造函数的第一个参数的错误:error C2664: 'std::unique_ptr&lt;_Ty,_Dx&gt;::unique_ptr(svm_model *,void (__cdecl *const &amp;)(svm_model *&amp;))' : cannot convert parameter 1 from 'svm_model *' to 'svm_model *' @Mihai Todor:这是一个奇怪的错误...尝试从删除函数和 unique_ptr 声明中删除引用。 我认为这可能是由一些 Visual Studio 2010 编译器问题引起的......而且显然不是由删除器构造引起的。

以上是关于使用带有自定义释放器的 std::unique_ptr 来包装原始指针的主要内容,如果未能解决你的问题,请参考以下文章

带有自定义单元格子视图的 UITableView - respondsToSelector:]:消息发送到已释放的实例

带有自定义 html5 控件的 Airplay

自定义 html5 视频播放器的控件元素中可用的按钮

带有 XNA 的随机音乐播放器

在测试中使用 unique_ptr 时出现无效指针错误

使用VideoView自定义一个播放器控件