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

Why unique_ptr and shared_ptr do not invalidate the pointer they are constructed from?

注意:这是一个API设计问题,基于[=18的构造函数的设计=] 和 share_ptr 为了这个问题,但不打算对他们当前的规范提出任何更改。


虽然通常建议使用 make_uniquemake_shared,但 unique_ptrshared_ptr 都可以从原始指针构造。

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

以下代码编译并产生双重释放:

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?
}

只要他们都有一个get()方法,使原来的指针失效就不是"more secured",而是更容易混淆的方法

在 C++ 中使用原始指针的方式中,它们本身不代表任何所有权概念,也不定义对象生命周期。任何在 C++ 中使用原始指针的人都需要知道它们仅在对象存在时有效(无论对象生命周期是由智能指针还是由程序逻辑强制执行)。从原始指针创建智能指针不 "transfer" 对象的所有权,但 "assigns" 它。

这个区别可以在下面的例子中清楚地说明:

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 对象的所有权不会转移给 ptr3ptr1 没有释放所有权就错误地分配给了它。程序员 需要 知道传递给 std::unique_ptr 构造函数的指针来自何处,以及到目前为止如何强制执行对象的所有权。库保证它将使这个特定的指针变量无效可能会给程序员一种错误的安全感,但并不能提供任何真正的安全。

我认为答案很简单:零开销。 unique_ptr 不需要此更改即可正常运行,因此标准不需要它。如果您认为这足以提高安全性,值得,您可以要求您的实现添加它(可能在特殊的编译标志下)。

顺便说一句,我希望静态分析器知道足够的知识来警告这种代码模式。