【发布时间】:2020-06-17 11:23:45
【问题描述】:
注意:这是一个 API 设计问题,为了这个问题,依赖于 unique_ptr 和 share_ptr 的构造函数的设计,但是不打算对他们当前的规范提出任何改变。
虽然通常建议使用make_unique 和make_shared,但unique_ptr 和shared_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_ptr 和 shared_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_shared 和make_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?
}
【问题讨论】:
-
因为你根本不应该使用 new,所以使用 std::make_unique 或 std::make_shared
-
一般情况是有other个值相等的指针。你如何将它们设置为'nullptr'?
-
@Caleth 这不会解决指向同一地址的其他指针的问题。理论上这是一般的情况,实际上远不是普通的情况。无论如何,在传递指针变量的情况下要求用户调用
std::move的语义使得传递所有权变得更加冗长 -
@MrTux 我会说提议的方法会鼓励更多使用 std::make_unique 或 std::make_shared,但因为已经有一个构造函数允许从原始指针创建也许它应该有不同的定义——这就是问题所在
-
通过原始指针的副本引用拥有的对象从未被认为是错误的,甚至是危险的。有时甚至是有益的:herbsutter.com/2013/06/05/…
标签: c++ shared-ptr api-design unique-ptr rvalue-reference