为什么 C++ 智能指针如此不受限制,即可以重新分配或重置?
Why are C++ smart pointers so unrestricted, i.e. may be reassigned or reset?
在C++11和boost中,智能指针都可以是nullptr。我想知道为什么。这意味着每次将智能指针从不受控制的客户端代码传递给接口方法时,都必须检查它们是否为 nullptr。很明显,这样的检查是在运行的时间内完成的。
如果有智能指针只能通过 make_shared 或 make_unique 创建并且不能重置或重新分配给 nullptr 或原始指针怎么办?这种方法可以确保指针在 compile 时间内不是 nullptr。
例如,在Java中我们总是必须检查对象是否不为空(坏)。但是在 Swift 中,我们可以明确地确保参数(或变量)在编译时不为空(好)。
更新:
嗯,非常感谢您的回答和评论。我明白了。但是是否有任何流行的库支持非无效编译时间保证以及所有权,也许是智能指针包装器?
std
智能指针的存在有一个原因——实现 所有权的概念。 他们的责任是明确定义谁拥有指针对象(即谁以及如何确保它的安全销毁)。
std
的大部分实际上是由低级基本构建块组成的。虽然它们可以直接在客户端代码中使用,但它们不应该是包罗万象的解决方案。他们为您提供单一用途的工具,您可以混合搭配这些工具来创建您需要的东西。
std
智能指针实际上是 "raw pointers + ownership." 原始指针可以为空并且可以重新设置,因此 std
智能指针也可以。没有什么能阻止您创建自己的“std
智能指针 + 非无效性”class(es) 并在您的代码中使用它们。
另一方面,空智能指针有非常有效的用例。如果 std
智能指针强制非空,并且您需要支持空的智能指针,那么实现它会困难得多。当您只能通过添加到原始 class.
时,添加有效性约束比删除它更容易
因为 std::unique_ptr
不可能要求没有 null,考虑这个:
std::unique_ptr<int> p = std::make_unique<int>();
std::unique_ptr<int> q = std::move(p);
什么值会有 p
和 q
?如果我们禁止 null 选项,这将无法实现。就算考虑毁招,情况也会更糟。这是因为你将不被允许测试p
,任何使用都将是UB。
对于 std::share_ptr
可能需要它,但这将严重阻碍可能使用可空指针的任何其他用途。标准库过于通用,不允许这种限制。
让指针指向的现有对象在编译时得到保证的总体想法非常有价值,但您尝试为此使用了错误的工具。通常这是通过使用 &
而不是指针来完成的。
为了解决您的需求,我建议围绕 std::share_ptr
:
创建 warper
template<typename T>
class always_ptr
{
std::shared_ptr<T> _ptr;
public:
always_ptr() = delete; //no default constructor
always_ptr(const always_ptr& a) : _ptr{ a._ptr } { }
explicit always_ptr(T* p)
{
if (!p) throw std::Exception(); //only way to guarantee this is not null
_ptr = std::shared_ptr<T>(p);
}
T* get() { return _ptr.get(); }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr.get(); }
explicit operator bool() const { return true; } //always true
};
在C++11和boost中,智能指针都可以是nullptr。我想知道为什么。这意味着每次将智能指针从不受控制的客户端代码传递给接口方法时,都必须检查它们是否为 nullptr。很明显,这样的检查是在运行的时间内完成的。
如果有智能指针只能通过 make_shared 或 make_unique 创建并且不能重置或重新分配给 nullptr 或原始指针怎么办?这种方法可以确保指针在 compile 时间内不是 nullptr。
例如,在Java中我们总是必须检查对象是否不为空(坏)。但是在 Swift 中,我们可以明确地确保参数(或变量)在编译时不为空(好)。
更新: 嗯,非常感谢您的回答和评论。我明白了。但是是否有任何流行的库支持非无效编译时间保证以及所有权,也许是智能指针包装器?
std
智能指针的存在有一个原因——实现 所有权的概念。 他们的责任是明确定义谁拥有指针对象(即谁以及如何确保它的安全销毁)。
std
的大部分实际上是由低级基本构建块组成的。虽然它们可以直接在客户端代码中使用,但它们不应该是包罗万象的解决方案。他们为您提供单一用途的工具,您可以混合搭配这些工具来创建您需要的东西。
std
智能指针实际上是 "raw pointers + ownership." 原始指针可以为空并且可以重新设置,因此 std
智能指针也可以。没有什么能阻止您创建自己的“std
智能指针 + 非无效性”class(es) 并在您的代码中使用它们。
另一方面,空智能指针有非常有效的用例。如果 std
智能指针强制非空,并且您需要支持空的智能指针,那么实现它会困难得多。当您只能通过添加到原始 class.
因为 std::unique_ptr
不可能要求没有 null,考虑这个:
std::unique_ptr<int> p = std::make_unique<int>();
std::unique_ptr<int> q = std::move(p);
什么值会有 p
和 q
?如果我们禁止 null 选项,这将无法实现。就算考虑毁招,情况也会更糟。这是因为你将不被允许测试p
,任何使用都将是UB。
对于 std::share_ptr
可能需要它,但这将严重阻碍可能使用可空指针的任何其他用途。标准库过于通用,不允许这种限制。
让指针指向的现有对象在编译时得到保证的总体想法非常有价值,但您尝试为此使用了错误的工具。通常这是通过使用 &
而不是指针来完成的。
为了解决您的需求,我建议围绕 std::share_ptr
:
template<typename T>
class always_ptr
{
std::shared_ptr<T> _ptr;
public:
always_ptr() = delete; //no default constructor
always_ptr(const always_ptr& a) : _ptr{ a._ptr } { }
explicit always_ptr(T* p)
{
if (!p) throw std::Exception(); //only way to guarantee this is not null
_ptr = std::shared_ptr<T>(p);
}
T* get() { return _ptr.get(); }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr.get(); }
explicit operator bool() const { return true; } //always true
};