使用带有自定义释放器的 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<_Ty,_Dx>::unique_ptr(svm_model *,void (__cdecl *const &)(svm_model *&))' : 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<svm_model, void(*)(svm_model *)>
(或std::function
变体)。然后,您可以使用对svm_load_model
的调用并提供删除器lambda 在构造函数的初始化列表中对其进行初始化。默认情况下,unique_ptr
将内部指针初始化为nullptr
,但我不确定自定义删除器的情况,但 VS2010 编译成功。稍后要对其进行初始化,您必须创建一个相同类型的临时unique_ptr
并将std::move
它放入p
【参考方案2】:
unique_ptr
的 deleter 应该是一个只接受一个指针的函数,而不是一个指向指针的指针。你可以试试:
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<_Ty,_Dx>::unique_ptr(svm_model *,void (__cdecl *const &)(svm_model *&))' : cannot convert parameter 1 from 'svm_model *' to 'svm_model *'
@Mihai Todor:这是一个奇怪的错误...尝试从删除函数和 unique_ptr 声明中删除引用。
我认为这可能是由一些 Visual Studio 2010 编译器问题引起的......而且显然不是由删除器构造引起的。以上是关于使用带有自定义释放器的 std::unique_ptr 来包装原始指针的主要内容,如果未能解决你的问题,请参考以下文章