为什么原始指针没有 "weak pointer"?或者有吗?
Why is there no "weak pointer" for raw pointer? Or is there?
毫无疑问,共享指针是个好主意。但只要大型程序包含原始指针,我认为使用共享指针就有很大的风险。主要是,您将失去对指向包含原始指针的对象的指针的真实生命周期的控制,并且错误将出现在更难以查找和调试的位置。
所以我的问题是,是否没有尝试向现代 c++ 添加一个不依赖于使用共享指针的 "weak pointer"?我的意思是只是一个指针,当在程序的任何部分删除时它会变成 NULL。有没有理由不使用这种自制的包装纸?
为了更好的解释我的意思,下面是我做的这样一个"weak pointer"。我把它命名为 WatchedPtr.
#include <memory>
#include <iostream>
template <typename T>
class WatchedPtr {
public:
// the only way to allocate new pointer
template <typename... ARGS>
WatchedPtr(ARGS... args) : _ptr (new T(args...)), _allocated (std::make_shared<bool>(true)) {}
WatchedPtr(const WatchedPtr<T>& other) : _ptr (other._ptr), _allocated (other._allocated) {}
// delete the pointer
void del () {delete _ptr; *_allocated = false;}
auto& operator=(const WatchedPtr<T> &other) { return *this = other; }
bool isNull() const { return *_allocated; }
T* operator->() const { return _ptr; }
T& operator*() const { return *_ptr; }
private:
T* _ptr;
std::shared_ptr <bool> _allocated;
};
struct S {
int a = 1;
};
int main () {
WatchedPtr<S> p1;
WatchedPtr<S> p2(p1);
p1->a = 8;
std::cout << p1.isNull () << std::endl;
std::cout << p2.isNull () << std::endl;
p2.del ();
std::cout << p1.isNull () << std::endl;
std::cout << p1.isNull () << std::endl;
return 0;
}
结果:
1
1
0
0
-已编辑-
谢谢大家。到目前为止的评论和答案之后的一些澄清:
- 我给出的WatchedPtr的实现只是为了说明我的意思:一个指针,不从外部分配中获取副本,不能从外部删除,删除后变为空。实施显然远非完美,也不意味着完美。
- 混合使用 shared_ptr 和原始指针的问题非常普遍:A* 被保存为原始指针,因此在程序的某些点创建并在程序的某些点明确删除。 B 持有一个 A*,而 B* 持有为 shared_ptr,因此 B* 具有模糊的生命周期。因此B在删除B持有的A*后可能会存活很长时间。
- "WatchedPtr"在我心目中的主要用法是防御性编程。即检查 null 并为连续性做最好的事情(和调试错误)。 shared_ptr 可以做到,但是以一种非常危险的方式 - 它会隐藏和延迟问题。
- 也可以有"WatchedPtr"的设计用法(很少且明确"owners"),但这不是主要思想。因为确实共享指针正在做这项工作。
- "WatchedPtr" 的目的不是一次替换程序中所有现有的原始指针。这与替换为 shared_ptr 的工作量不同,恕我直言,它已为整个程序一次性完成。这对于大型程序来说是不现实的。
弱指针依赖于来自智能指针基础结构的通知,因此您永远无法使用实际的原始指针执行此操作。
可以想象 unique_ptr
的扩展当然支持弱指针。大概没有人急于实现这样一个特性的主要原因是弱指针已经在规模的"Use refcounting and everything should just work"端,而unique_ptr
在规模的"Manage your lifetimes through RAII or you're not a real C++ programmer"端。弱指针还要求每个分配有一个单独的控制块,这意味着与 shared_ptr
相比,这种 WatchedPtr
的性能优势将是最小的。
I think there is a big risk in using shared pointers. Mainly, you will loose control of the real life-cycle of pointers to objects that hold raw pointers, and bugs will occur in locations which are more difficult to find and debug.
那你说
just a pointer which becomes NULL when deleted in any part of the program.
你没看出矛盾吗?
您不想使用共享指针,因为对象的生命周期是在运行时确定的。到目前为止一切顺利。
但是,您希望指针在所有者删除时自动变为空。问题是如果你的指针的生命周期是已知的,你根本不需要它!如果您知道指针的生命周期何时结束,那么您应该能够删除该指针的所有实例,并且可以检查指针是否已死。
如果你有一个指针,你不知道所有者什么时候会释放它并且无法检查或者从弱所有者的角度来看没有可观察到的副作用,那么你真的可以控制你的指针的寿命?不是真的。
事实上,您的实现依赖于包含一个共享指针。从某种意义上说,这是有启发性的,因为您需要某种形式的共享所有权才能实现可以具有指向它的弱指针的原始指针。然后,如果您需要共享所有权来实现具有弱引用的原始指针,您将得到一个共享指针。这就是为什么你提出的 class 的存在是矛盾的。
std::shared_ptr
+ std::weak_ptr
是为了解决"parts of your program don't know when the owner free the resouce"的问题。您需要的是单个 std::shared_ptr
和多个 std::weak_ptr
,以便它们知道资源何时被释放。这些 classes 具有在运行时检查变量生命周期所需的信息。
或者相反,如果您知道指针的生命周期,则使用该知识并找到一种方法来删除悬空指针,或者公开一种检查悬空指针的方法。
阅读答案和评论以及 C++ Core Guidelines by Bjarne Stroustrup & Herb Sutter,我得出以下答案:
遵循指南时,不需要涉及 "new" 和 "delete" 的 "WatchedPtr"。但是,出于 debug/QA 目的,一种跟踪从智能指针获取的原始指针的有效性的方法对我来说仍然是个问题。
详情:
应继续使用原始指针。由于各种原因。但是,显式 "new" 和 "delete" 不应该。调用"new"和"delete"的情况应该全部替换为shared_ptr/unique_ptr。
在当前分配原始指针的地方,将其替换为 "WatchedPtr".
没有意义
如果将原始指针替换为分配给它的其他东西,在大多数情况下它将替换为 unique_ptr,而在其他情况下将替换为 shared_ptr。 "WatchedPtr",如果有的话,将从那个点继续,从 shared/unique 指针构建。
因此我有 posted a somewhat different question.
毫无疑问,共享指针是个好主意。但只要大型程序包含原始指针,我认为使用共享指针就有很大的风险。主要是,您将失去对指向包含原始指针的对象的指针的真实生命周期的控制,并且错误将出现在更难以查找和调试的位置。
所以我的问题是,是否没有尝试向现代 c++ 添加一个不依赖于使用共享指针的 "weak pointer"?我的意思是只是一个指针,当在程序的任何部分删除时它会变成 NULL。有没有理由不使用这种自制的包装纸?
为了更好的解释我的意思,下面是我做的这样一个"weak pointer"。我把它命名为 WatchedPtr.
#include <memory>
#include <iostream>
template <typename T>
class WatchedPtr {
public:
// the only way to allocate new pointer
template <typename... ARGS>
WatchedPtr(ARGS... args) : _ptr (new T(args...)), _allocated (std::make_shared<bool>(true)) {}
WatchedPtr(const WatchedPtr<T>& other) : _ptr (other._ptr), _allocated (other._allocated) {}
// delete the pointer
void del () {delete _ptr; *_allocated = false;}
auto& operator=(const WatchedPtr<T> &other) { return *this = other; }
bool isNull() const { return *_allocated; }
T* operator->() const { return _ptr; }
T& operator*() const { return *_ptr; }
private:
T* _ptr;
std::shared_ptr <bool> _allocated;
};
struct S {
int a = 1;
};
int main () {
WatchedPtr<S> p1;
WatchedPtr<S> p2(p1);
p1->a = 8;
std::cout << p1.isNull () << std::endl;
std::cout << p2.isNull () << std::endl;
p2.del ();
std::cout << p1.isNull () << std::endl;
std::cout << p1.isNull () << std::endl;
return 0;
}
结果:
1
1
0
0
-已编辑-
谢谢大家。到目前为止的评论和答案之后的一些澄清:
- 我给出的WatchedPtr的实现只是为了说明我的意思:一个指针,不从外部分配中获取副本,不能从外部删除,删除后变为空。实施显然远非完美,也不意味着完美。
- 混合使用 shared_ptr 和原始指针的问题非常普遍:A* 被保存为原始指针,因此在程序的某些点创建并在程序的某些点明确删除。 B 持有一个 A*,而 B* 持有为 shared_ptr,因此 B* 具有模糊的生命周期。因此B在删除B持有的A*后可能会存活很长时间。
- "WatchedPtr"在我心目中的主要用法是防御性编程。即检查 null 并为连续性做最好的事情(和调试错误)。 shared_ptr 可以做到,但是以一种非常危险的方式 - 它会隐藏和延迟问题。
- 也可以有"WatchedPtr"的设计用法(很少且明确"owners"),但这不是主要思想。因为确实共享指针正在做这项工作。
- "WatchedPtr" 的目的不是一次替换程序中所有现有的原始指针。这与替换为 shared_ptr 的工作量不同,恕我直言,它已为整个程序一次性完成。这对于大型程序来说是不现实的。
弱指针依赖于来自智能指针基础结构的通知,因此您永远无法使用实际的原始指针执行此操作。
可以想象 unique_ptr
的扩展当然支持弱指针。大概没有人急于实现这样一个特性的主要原因是弱指针已经在规模的"Use refcounting and everything should just work"端,而unique_ptr
在规模的"Manage your lifetimes through RAII or you're not a real C++ programmer"端。弱指针还要求每个分配有一个单独的控制块,这意味着与 shared_ptr
相比,这种 WatchedPtr
的性能优势将是最小的。
I think there is a big risk in using shared pointers. Mainly, you will loose control of the real life-cycle of pointers to objects that hold raw pointers, and bugs will occur in locations which are more difficult to find and debug.
那你说
just a pointer which becomes NULL when deleted in any part of the program.
你没看出矛盾吗?
您不想使用共享指针,因为对象的生命周期是在运行时确定的。到目前为止一切顺利。
但是,您希望指针在所有者删除时自动变为空。问题是如果你的指针的生命周期是已知的,你根本不需要它!如果您知道指针的生命周期何时结束,那么您应该能够删除该指针的所有实例,并且可以检查指针是否已死。
如果你有一个指针,你不知道所有者什么时候会释放它并且无法检查或者从弱所有者的角度来看没有可观察到的副作用,那么你真的可以控制你的指针的寿命?不是真的。
事实上,您的实现依赖于包含一个共享指针。从某种意义上说,这是有启发性的,因为您需要某种形式的共享所有权才能实现可以具有指向它的弱指针的原始指针。然后,如果您需要共享所有权来实现具有弱引用的原始指针,您将得到一个共享指针。这就是为什么你提出的 class 的存在是矛盾的。
std::shared_ptr
+ std::weak_ptr
是为了解决"parts of your program don't know when the owner free the resouce"的问题。您需要的是单个 std::shared_ptr
和多个 std::weak_ptr
,以便它们知道资源何时被释放。这些 classes 具有在运行时检查变量生命周期所需的信息。
或者相反,如果您知道指针的生命周期,则使用该知识并找到一种方法来删除悬空指针,或者公开一种检查悬空指针的方法。
阅读答案和评论以及 C++ Core Guidelines by Bjarne Stroustrup & Herb Sutter,我得出以下答案:
遵循指南时,不需要涉及 "new" 和 "delete" 的 "WatchedPtr"。但是,出于 debug/QA 目的,一种跟踪从智能指针获取的原始指针的有效性的方法对我来说仍然是个问题。
详情:
应继续使用原始指针。由于各种原因。但是,显式 "new" 和 "delete" 不应该。调用"new"和"delete"的情况应该全部替换为shared_ptr/unique_ptr。
在当前分配原始指针的地方,将其替换为 "WatchedPtr".
没有意义
如果将原始指针替换为分配给它的其他东西,在大多数情况下它将替换为 unique_ptr,而在其他情况下将替换为 shared_ptr。 "WatchedPtr",如果有的话,将从那个点继续,从 shared/unique 指针构建。
因此我有 posted a somewhat different question.