用智能指针缓解遗留API的内存泄漏

后端开发 2026-07-09

在我的项目中,我有一个相当糟糕设计的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 代码一样安全(也就是说,即使在 ultraLegacyApicalloc 之后抛出并赋值给 *y 时,也会释放内存),并且看起来相当漂亮。

理论上它甚至可以实现你想用 unique_ptr 做的事情(但做不到,因为“没有办法获取托管指针的地址”),因为 out_ptr_tunique_ptr 可以被STL的实现者定制,以了解彼此的内部细节。

唯一明显的问题是——它需要C++23(不过如果你确实想在较旧的标准下使用它,也可以获得它的C++11实现)。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章