【问题标题】:Why is there no "weak pointer" for raw pointer? Or is there?为什么原始指针没有“弱指针”?或者有吗?
【发布时间】:2019-08-25 06:42:21
【问题描述】:

毫无疑问,共享指针是个好主意。但只要大型程序包含原始指针,我认为使用共享指针存在很大风险。主要是,您将失去对指向包含原始指针的对象的指针的实际生命周期的控制,并且错误将出现在更难查找和调试的位置。

所以我的问题是,是否没有尝试向现代 c++ 添加不依赖于使用共享指针的“弱指针”?我的意思是在程序的任何部分中删除时都会变为 NULL 的指针。有理由不用这样的自制包装器吗?

为了更好地解释我的意思,下面是我做的这样一个“弱指针”。我将其命名为 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

-已编辑-

谢谢大家。到目前为止的cmets和答案之后的一些澄清:

  • 我为 WatchedPtr 提供的实现只是为了说明我的意思:一个指针不能从外部分配中获取副本,不能从外部删除,如果删除则变为 null。实施远非完美,也并非完美。
  • shared_ptr 和原始指针混合的问题很常见:​​A* 被保存为原始指针,因此在程序的某个点创建并在程序的某个点显式删除。 B 持有 A*,B* 持有 shared_ptr,因此 B* 具有模糊的寿命。因此,在 B 持有的 A* 被删除后,B 可能会存活很长时间。
  • 在我看来,“WatchedPtr”的主要用途是防御性编程。即检查 null 并尽最大可能确保连续性(和调试错误)。 shared_ptr 可以做到,但以一种非常危险的方式 - 它会隐藏和延迟问题。
  • “WatchedPtr”也可以有设计用途(非常少且明确的“所有者”),但这不是主要思想。为此,共享指针确实起到了作用。
  • “WatchedPtr”的目的不是一次性替换程序中所有现有的原始指针。这与替换为 shared_ptr 的工作不同,恕我直言,它已经为整个程序一次性完成。这对于大型项目来说是不现实的。

【问题讨论】:

  • 如果您已经在更改代码以使用某种类型的指针包装器,那么为什么不将其更改为正确使用正确的智能指针呢?
  • 这不是线程安全的。尽管您使用了共享指针,但一个线程可能正在使用该指针,而另一个线程将其删除。只有在它被删除后,才会告诉线程“不,它不再存在”。这里有一个 UB 的窗口 AFAICT。
  • 您需要原始指针,而不是在无用时传递共享指针。如果您的设计内存响应正确,那么弱原始指针的概念就没有实际意义。
  • 我很好奇你为什么想要这个。你基本上有一个原始指针。你能举一个你想做的例子吗? unique_ptr、shared_ptr 和 weak_ptr 几乎可以满足您需要的所有所有权机制。
  • std::weak_ptr 的一个重要安全功能是在使用它之前将其锁定,只要您需要使用引用的对象,就会生成一个std::shared_ptr。使用您的解决方案,无法防止破坏指向的对象。即使这不是一个问题,看起来你正在实施一个半措施。你保留了原始的拥有指针并实现了std::shared_ptr 的一些优点。它不能解决您仍在使用原始拥有指针的基本问题。该更改并没有使代码库比您发现的更好。

标签: c++ c++11 smart-pointers


【解决方案1】:

弱指针依赖于来自智能指针基础设施的通知,因此您永远无法使用实际的原始指针来做到这一点。

你可以想象一个扩展,比如说,unique_ptr,它当然支持弱指针。据推测,没有人急于实现这样一个特性的主要原因是弱指针已经处于“使用引用计数,一切都应该正常工作”的规模末端,而 unique_ptr 处于“通过 RAII 或你管理你的生命周期” “不是一个真正的 C++ 程序员”。弱指针还要求每个分配有一个单独的控制块,这意味着与shared_ptr 相比,这种WatchedPtr 的性能优势将是最小的。

【讨论】:

  • 这取决于。我不喜欢shared_ptr 基础设施的一件事是它们默认是线程安全的(无法选择退出)。这意味着任何引用计数都需要通过类似原子的类,该类引入了具有合理成本的屏障/其他机制,尤其是在 non x86/x64 架构上。所以中间的东西是可能的(在特殊情况下很有用)
  • @MikeVine 但是这真的是个问题。您多久更改或转让所有权?通常,只有当您将对象从一个线程传递到另一个线程时才会发生这种情况(因此是任务的开始和结束)。如果设计正确,您需要获得临时所有权的情况也应该很少见。在大多数情况下,你的代码应该设计成如果一个函数接受一个对象作为参数,这个函数不需要所有权,并且传递托管原始指针就足够了。
【解决方案2】:

我认为使用共享指针存在很大风险。主要是,您将失去对指向包含原始指针的对象的指针的实际生命周期的控制,并且错误将出现在更难查找和调试的位置。

那你说

只是一个指针,当在程序的任何部分被删除时变为 NULL。

你没看到矛盾吗?

您不想使用共享指针,因为对象的生命周期是在运行时确定的。到目前为止一切顺利。

但是,您需要一个在所有者删除它时自动变为空的指针。问题是如果你的指针的生命周期是已知的,你根本不需要它!如果您知道指针的生命周期何时结束,那么您应该能够删除该指针的所有实例,从而检查指针是否已死。

如果你有一个不知道所有者何时释放它的指针,并且以弱所有者的观点无法检查或没有可观察到的副作用,那么你真的可以控制生命周期吗?你的指针?不是真的。

事实上,您的实现依赖于包含共享指针。从某种意义上说,这是很有启发性的,您需要某种形式的共享所有权才能实现可以具有弱指针的原始指针。然后,如果您需要共享所有权来实现具有弱引用的原始指针,那么您将得到一个共享指针。这就是为什么你提出的类的存在是矛盾的。

std::shared_ptr + std::weak_ptr 用于处理“您的程序的一部分不知道所有者何时释放资源”的问题。你需要的是一个std::shared_ptr 和多个std::weak_ptr,这样他们就知道什么时候资源被释放了。这些类具有在运行时检查变量生命周期所需的信息。

或者相反,如果您知道指针的生命周期,则使用该知识并找到一种方法来删除悬空指针,或公开一种检查悬空指针的方法。

【讨论】:

    【解决方案3】:

    阅读答案和cmets,以及C++ Core Guidelines by Bjarne Stroustrup & Herb Sutter,我得出了以下答案:
    遵循指南时,不需要涉及“新建”和“删除”的“WatchedPtr”。但是,出于调试/QA 的目的,我仍然在考虑一种方法来跟踪从智能指针获取的原始指针的有效性。

    详细说明:

    应继续使用原始指针。由于各种原因。但是,明确的“新建”和“删除”不应该。调用“new”和“delete”的情况都应该用shared_ptr/unique_ptr代替。
    在当前分配原始指针的地方,用“WatchedPtr”替换它是没有意义的。
    如果将原始指针替换到分配它的其他位置,则在大多数情况下将指向 unique_ptr,而在其他情况下将指向 shared_ptr。如果有的话,“WatchedPtr”将从该点继续,由共享/唯一指针构建。

    因此我有posted a somewhat different question。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-07
      • 1970-01-01
      • 2013-05-21
      • 2018-06-01
      • 2023-01-08
      • 1970-01-01
      相关资源
      最近更新 更多