25 March 2022

C++ 核心指南目录

I.11: Never transfer ownership by a raw pointer (T*) or reference (T&)

理由

如果不清楚某对象的所有权是属于调用者还是被调用者,可能会导致内存泄漏,或不完全的对象析构。

例子

struct X { int x; };
X* compute(int y)    // don't
{
    X* res = new X{};
    // ...
    cout << res << endl;
    return res;
}
int main()
{
    auto xx = compute(0);
    cout << xx;
    return 0;
}
0x208072a27f0
0x208072a27f0

谁来删除函数 compute 返回的 X ?如果 compute 返回的是引用,则此问题更难定位。考虑数值返回。如果返回结果很大,可以使用移动机制。

vector<double> compute(int x)  // good
{
    vector<double> res(10000);
    // ...
    res[0] = x;
    return res;
}
int main()
{
    auto xx = compute(999);
    cout << xx[0];
    return 0;
}
999

替代方案

通过智能型指针传递所有权。比如unique_ptr(所有权互斥), shared_ptr (所有权共享)。然而,这个方法并不优雅,相比返回对象本身,性能可能会差。

替代方案

有时候因为 ABI 兼容要求,无法修改老的代码,或因为资源不足。这时,可以使用 GSL 的 owner 标注指针所有者。

#include <gsl/gsl>
using namespace gsl;
struct X {int x;};
owner<X*> compute(int x)    // It is now clear that ownership is transferred
{
    owner<X*> res = new X{};
    // ...
    cout << res << endl;
    return res;
}
int main()
{
    auto xx = compute(10);
    cout << xx;
    return 0;
}
0x20d54de27f0
0x20d54de27f0

这里我们告诉代码分析工具, res 是一个 owner 。它的值必须删除或转移给其他所有者。这里通过 return 传递所有权。

注意

以原始指针传递的对象,其所有者默认就是调用者。因此,其生存周期由调用者处理。从另一个角度看,所有权转移的接口比指针传递接口用的更少,所以默认是不进行所有权转移的。

强化

  • (简单) deleteowner<T> 的原始指针的时候,发出警告。建议使用标准库的资源处理程序,或使用 owner<T>
  • (简单) 在重置或显示删除有主指针的时候,发出警告。
  • (简单)返回值是 new 或函数调用返回值是 owner 指针但是赋给原始指针或无主引用的时候,发出警告。