为什么unique_ptr和shared_ptr不会使它们的构造指针无效?

时间:2020-03-04 21:33:20

标签: c++ shared-ptr api-design unique-ptr rvalue-reference

注意:这是一个 API设计问题 ,出于unique_ptrshare_ptr的构造函数的设计考虑问题,但并非旨在提出对当前规范的任何更改。


尽管通常建议使用make_uniquemake_shared,但unique_ptrshared_ptr都可以由原始指针构造。

两者都按值获取指针并进行复制。两者都允许(即:不要阻止)继续使用构造函数中传递给它们的原始指针。

以下代码使用double free编译和结果:

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

如果unique_ptrshared_ptr的相关构造函数希望将原始指针作为 rvalue 来获取,例如,对于unique_ptr:

template<typename T>
class unique_ptr {
  T* ptr;
public:
  unique_ptr(T*&& p) : ptr{p} {
    p = nullptr; // invalidate the original pointer passed
  }
  // ...

因此,原始代码无法编译为 lvalue 无法绑定到 rvalue ,但是使用std::move可以编译代码,同时更加冗长且更安全:

int* ptr = new int(9);
std::unique_ptr<int> p { std::move(ptr) };
if (!ptr) {
  // we are here, since ptr was invalidated
}

很明显,用户可以使用智能指针来处理许多其他错误。 您通常应该知道如何正确使用语言提供的工具,而 C ++并不是为了监视您等而设计的。

但是,似乎仍然可以选择防止这个简单的错误并鼓励使用make_sharedmake_unique。甚至在C ++ 14中添加make_unique之前,仍然总是可以选择不使用指针变量的直接分配,例如:

auto ptr = std::unique_ptr<int>(new int(7));

似乎向指针请求右值引用作为构造函数参数可能会增加一些额外的安全性。此外,由于我们拥有传递的指针的所有权,因此获取 rvalue 的语义似乎更加准确。

提到标准为何不采用这种更安全的方法?


可能的原因可能是上述建议的方法将阻止从 const指针 创建unique_ptr,即以下代码将无法使用建议的方法:

int* const ptr = new int(9);
auto p = std::unique_ptr { std::move(ptr) }; // cannot bind `const rvalue` to `rvalue`

但是,我认为这似乎是一个值得忽略的罕见情况。

或者,如果需要从const指针支持初始化是强烈反对所提出方法的论点,那么使用以下方法仍可以实现较小的步骤:

unique_ptr(T* const&& p) : ptr{p} {
    // ...without invalidating p, but still better semantics?
}

2 个答案:

答案 0 :(得分:4)

只要它们都具有get()方法,使原始指针无效并不是一种“更安全”的方法,而是一种更加混乱的方法。

在C ++中使用原始指针的方式,它们本身并不表示任何所有权概念,也不定义对象寿命。在C ++中使用原始指针的任何人都需要意识到,它们仅在对象存在时才有效(无论对象寿命是由智能指针还是由程序逻辑来强制执行)。从原始指针创建智能指针不会“转移”对象的所有权,而是“分配”它。

在下面的示例中可以清楚地表明这种区别:

std::unique_ptr<int> ptr1 = std::make_unique<int>(1);
int* ptr2 = ptr1.get();
// ...
// somewhere later:
std::unique_ptr<int> ptr3(ptr2);
// or std::unique_ptr<int> ptr3(std::move(ptr2));

在这种情况下,int对象的所有权不会转移到ptr3。它被错误地分配给它,而没有ptr1释放所有权。程序员需要知道传递给std::unique_ptr构造函数的指针从何而来,以及到目前为止如何实现了对象的所有权。库保证它将使该特定的指针变量无效,这可能给程序员带来错误的安全感,但并没有提供任何真正的安全性。

答案 1 :(得分:1)

我认为答案很简单:零开销。不需要此更改即可使unique_ptr起作用,因此标准不需要。如果您认为这样可以提高安全性,使其值得实现,则可以要求您的实现添加它(也许在特殊的编译标志下)。

顺便说一句,我希望静态分析器了解的足够多,可以针对这种代码模式发出警告。