为什么unique_ptr和shared_ptr不会使它们所构建的指针失效?[英] Why unique_ptr and shared_ptr do not invalidate the pointer they are constructed from?

本文是小编为大家收集整理的关于为什么unique_ptr和shared_ptr不会使它们所构建的指针失效?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

一个注意:这是 api设计问题 ,骑在unique_ptr和share_ptr的构造函数的设计上,为了提出问题,但是没有旨在提出任何改变他们目前的规格.


虽然通常建议使用make_unique和make_shared,但是,均可从原始指针构成和shared_ptr.

两个通过值获取指针并复制它.允许(即,在意义上:不要阻止)在构造函数中传递给它们的原始指针的连续使用.

以下代码编译和结果双重免费:

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

unique_ptr和shared_ptr如果您的相关构造函数期望将原始指针作为 rvalue 获得原始指针,则可以防止上述内容. for 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_shared和make_unique.甚至在C ++ 14中添加make_unique之前,仍然总是在没有指针变量的情况下选择直接分配,如:

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

似乎将 rvalue参考指向指针,因为构造函数参数可以添加一些额外的安全性.此外,获取 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?
}

推荐答案

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

btw,我希望静态分析仪知道足以警告此代码模式.

其他推荐答案

只要它们都有一个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释放所有权. Programmer 需要要意识到传递给std::unique_ptr构造函数的指针来自于到目前为止的对象的所有权如何强制执行.库保证它将使这个特殊的指针变量无效可以使程序员成为假的安全感,但没有提供任何真正的安全性.

本文地址:https://www.itbaoku.cn/post/1937848.html

问题描述

A note: this is an API design question, riding on the design of the constructors of unique_ptr and share_ptr for the sake of the question, but not aiming to propose any change to their current specifications.


Though it would usually be advisable to use make_unique and make_shared, both unique_ptr and shared_ptr can be constructed from a raw pointer.

Both get the pointer by value and copy it. Both allow (i.e. in the sense of: do not prevent) a continuance usage of the original pointer passed to them in the constructor.

The following code compiles and results with double free:

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

Both unique_ptr and shared_ptr could prevent the above if their relevant constructors would expect to get the raw pointer as an rvalue, e.g. for unique_ptr:

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

Thus, the original code would not compile as an lvalue cannot bind to an rvalue, but using std::move the code compiles, while being more verbose and more secured:

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

It is clear that there can be dozens of other bugs a user can do with smart pointers. The commonly used argument of you should know how to properly use the tools provided by the language, and C++ is not designed to watch over you etc.

But still, it seems that there could have been an option for preventing this simple bug and to encourage usage of make_shared and make_unique. And even before make_unique was added in C++14, there is still always the option of direct allocation without a pointer variable, as:

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

It seems that requesting rvalue reference to a pointer as the constructor parameter could add a bit of an extra safety. Moreover, the semantics of getting rvalue seems to be more accurate as we take ownership of the pointer that is passed.

Coming to the question of why didn't the standard take this more secured approach?


A possible reason might be that the approach suggested above would prevent creating a unique_ptr from const pointers, i.e. the following code would fail to compile with the proposed approach:

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

But this seems to be a rare scenario worth neglecting, I believe.

Alternatively, in case the need to support initialization from a const pointer is a strong argument against the proposed approach, then a smaller step could still be achieved with:

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

推荐答案

I think the answer is simple: zero overhead. This change isn't needed for unique_ptr to be functional, so the standard doesn't require it. If you think this improves safety enough to worth it, you can ask your implementation to add it (maybe under a special compilation flag).

BTW, I'd expect static analyzers to know enough to warn against this code pattern.

其他推荐答案

As long as they both have a get() method, invalidating the original pointer is not a "more secured", but a more confusing approach.

In the way raw pointers are used in C++, they don't represent any ownership concept by themselves and don't define the object lifetime. Anyone who uses raw pointers in C++ needs to be aware that they are only valid while the object exists (whether the object lifetime is enforced by smart pointers or by the logic of the program). Creating a smart pointer from a raw pointer does not "transfer" ownership of the object, but "assigns" it.

This distinction could be made clear in the following example:

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));

In this case, the ownership of the int object is not transferred to the ptr3. It is erroneously assigned to it without releasing the ownership by ptr1. A programmer needs to be aware where the pointer passed to the std::unique_ptr constructor comes from, and how the ownership of the object has been enforced so far. An assurance by the library that it will invalidate this particular pointer variable may give the programmer a false sense of security, but does not provide any real safety.