用智能指针缓解遗留API的内存泄漏
在我的项目中,我有一个相当糟糕设计的API,我不被鼓励(或根本被禁止)去修改:
#include <vector>
#include <cstring>
#include <iostream>
// absolutely no control over this part of the code
const std::vector<int> DATA{1, 2, 3, 4};
void ultraLegacyApi(void** y) {
*y = calloc(DATA.size(), sizeof(int));
std::memcpy(*y, DATA.data(), DATA.size()*sizeof(int));
}
// cannot change signature, should not modify too much of the body
void* badGetVector() {
const int len = 4; // magically known length
void *y;
// here some boilerplate code, branching, early return, and whatnot
ultraLegacyApi(&y);
return new std::vector<int>((int*)y, (int*)y + len);
}
接着,在我的客户端代码中,我可以通过将结果包裹在 unique_ptr 来缓解泄漏的 std::vector,示例如下:
std::vector<int> getIVector() {
auto dataPtr = std::unique_ptr<void, void (*)(void*)>(badGetVector(),
[](void* ptr) { delete static_cast<std::vector<int>*>(ptr); });
if (!dataPtr) throw std::runtime_error("oupsie");
return *static_cast<std::vector<int>*>(dataPtr.get());
}
int main()
{
auto intVec = getIVector();
std::cout << intVec[1] << "\n";
}
然而这仍然会泄漏,因为内部 badGetVector 不会释放由 ultraLegacyApi 分配的内存。我确实看不到在不修改 badGetVector 的实现的情况下缓解这个内部泄漏的可能性,因此我提出了这个小改动:
// cannot change signature, should not modify too much of the body
void* badGetVector() {
const int len = 4; // magically known length
void *y = nullptr;
std::shared_ptr<void> scopeGuard(nullptr, [&y](void*) {
free(y);
});
// here some boilerplate code, branching, early return, and whatnot
ultraLegacyApi(&y);
return new std::vector<int>((int*)y, (int*)y + len);
}
这确实可行,但看起来有点蠢——使用 shared_ptr 来创建一个作用域守卫,以模拟对尚未初始化的 void* 的唯一所有权。我本来想像成这样使用 unique_ptr:
std::unique_ptr<void, void (*)(void*)> scopeGuard(y, [](void* ptr) { free(ptr); });
// ...
ultraLegacyApi(&scopeGuard.get());
但显然它行不通,因为 get() 是通过拷贝返回托管指针,无法获取托管指针的地址。
是否存在更简洁和/或更健壮的方式来缓解由这个遗留API引发的泄漏?
解决方案
你正在寻找 out_ptr_t:
// cannot change signature, should not modify too much of the body
void* badGetVector() {
const int len = 4; // magically known length
struct Freer { static void operator()(int* ptr) { free(ptr);} };
std::unique_ptr<int[], Freer> ptr;
ultraLegacyApi(std::out_ptr<void*>(ptr));
return new std::vector<int>(ptr.get(), ptr.get() + len);
}
这应该和你的 shared_ptr 代码一样安全(也就是说,即使在 ultraLegacyApi 在 calloc 之后抛出并赋值给 *y 时,也会释放内存),并且看起来相当漂亮。
理论上它甚至可以实现你想用 unique_ptr 做的事情(但做不到,因为“没有办法获取托管指针的地址”),因为 out_ptr_t 和 unique_ptr 可以被STL的实现者定制,以了解彼此的内部细节。
唯一明显的问题是——它需要C++23(不过如果你确实想在较旧的标准下使用它,也可以获得它的C++11实现)。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。