注意:这是一个API设计问题,基于
尽管使用
两者都通过值获取指针并将其复制。它们都允许(即:不阻止)在构造函数中传递的原始指针继续使用。
以下代码编译并导致双重释放:
因此,原始代码将无法编译,因为lvalue无法绑定到rvalue上,但是使用
很明显,用户使用智能指针时可能会遇到许多其他的错误。常见的论点是“你应该知道如何正确地使用语言提供的工具”,以及“C++不是为了看守你而设计的”等等。
但是,似乎仍然有一种防止这种简单错误并鼓励使用make_shared和make_unique的选项。即使在C++14之前添加了make_unique,也仍然存在直接分配而不使用指针变量的选项,如下所示:
似乎将指针作为构造函数参数请求“右值引用”可能会增加一些额外的安全性。此外,获取“右值”的语义似乎更准确,因为我们拥有传递的指针。
现在谈到为什么标准没有采用这种更安全的方法?
一个可能的原因是,上述建议的方法将防止从const指针创建unique_ptr,也就是说,使用所提出的方法编译以下代码会失败:
unique_ptr
和share_ptr
构造函数的设计,仅用于问题描述,并不旨在提出对其当前规格的任何更改。
尽管使用
make_unique
和make_shared
通常是明智的,但是unique_ptr
和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传递,例如对于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));
似乎将指针作为构造函数参数请求“右值引用”可能会增加一些额外的安全性。此外,获取“右值”的语义似乎更准确,因为我们拥有传递的指针。
现在谈到为什么标准没有采用这种更安全的方法?
一个可能的原因是,上述建议的方法将防止从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?
}