【问题标题】:Are smart pointers capable of removing other references to its object from containers?智能指针是否能够从容器中删除对其对象的其他引用?
【发布时间】:2017-04-24 21:47:03
【问题描述】:

我想实现这个:

  • 对象 A 拥有一个对象 B(有一个指向它的指针)
    • 当对象 A 被销毁时,对象 B 也被销毁。
  • 对象 C 有一个 std::vector 指向对象 B-s 的指针。
    • 当对象 B 被销毁时,将其指针从对象 C 的向量中移除。

这可以通过不同智能指针的组合实现吗?

【问题讨论】:

  • weak pointer 对您有用吗? (不完全符合您的要求,但这是此类问题的标准方法。)
  • 类似struct A {std::unique_ptr<B> b;};struct C { std::vector<std::weak_ptr<B>> bs; }; ?
  • 这意味着Object B 或其智能指针 必须对包含(智能?)指针的每个vector 有某种引用给它。这听起来不太实用。
  • @Jarod42 这就是我在想的,但我不知道如果我有一个无限循环,在其中我在C的Bs上调用一个方法,检查weak_ptr是不是有点无效。 lock() 每个周期,而不是在 B 被破坏时仅向 C 发出信号?这种弱指针存储非常安全,但我不知道它是否足够有效,例如对于游戏引擎。
  • @Tudvari: 一种可能的方法是将B 标记为已删除,然后让C 从其向量中删除和移除对象……或者创建一个observer

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


【解决方案1】:

让我们从角色的角度来考虑(现在忽略线程):

对象 A 拥有 B 的生命周期

对象 C 是 B 生命周期的观察者

你没有说 A 和 C 之间是否存在关系,所以我假设 A 在其构造函数的点上知道 C 的参与(让我们使用 C 作为可配置工厂)。

B 的生命周期事件可以在 2 个地方创建观察 - B 的构造函数/析构函数(差 - 紧耦合)或中间工厂(更好 - 松耦合)。

所以:

#include <memory>
#include <algorithm>

struct B {

};

struct C {
    std::shared_ptr<B> make_b() {
        auto p = std::shared_ptr<B>(new B(), [this](B *p) {
            this->remove_observer(p);
            delete p;
        });
        add_observer(p.get());
        return p;
    }

private:
    void add_observer(B *p) {
        observers_.push_back(p);
    }

    void remove_observer(B *p) {
        observers_.erase(std::remove(std::begin(observers_), std::end(observers_), p),
                         std::end(observers_));
    }

    std::vector<B *> observers_;
};

struct A {
    A(C &factory)
            : b_(factory.make_b()) {}


    std::shared_ptr<B> b_;

};


int main() {

    // note: factory must outlive a1 and a2
    C factory;

    A a1(factory);
    A a2(factory);
}

请注意,虽然我使用了 shared_ptr,但在这种情况下,我可以轻松地使用 unique_ptr。但是,我会在指针中将 A 与删除器类型耦合 - 所以我要么必须创建自己的类型擦除删除器类型,要么将 A 更紧密地耦合到 C(我想避免这种情况)。

【讨论】:

    【解决方案2】:
    Object A owns an Object B (has a pointer to it)
        When Object A is destroyed, Object B is destroyed too.
    Object C has a std::vector of pointers to Object B-s.
        When Object B is destroyed, remove its pointer from Object C's vector.
    

    对象 A 的生命周期可以由 shared_ptr 管理。

    它可以完全控制B 的生命周期:

    struct A {
      std::unique_ptr<B> b;
    };
    

    struct A {
      B b;
    };
    

    我们将添加一个observe_B 方法:

    struct A {
      std::unique_ptr<B> b;
      B* observe_B() { return b.get(); }
      B const* observe_B() const { return b.get(); }
    };
    

    我们将在逻辑上将其设为 const。对于我们有一个实际的B 的情况,我们只需使用&amp; 而不是.get()。所以我们不再关心B 是如何分配的(指针或在A 的主体中)。

    现在我们有一个相对复杂的生命周期请求。在这里明智地使用shared_ptr 可能是合适的。其实shared_from_this

    struct A:std::enable_shared_from_this<A> {
      std::unique_ptr<B> b;
      B* observe_B() { return b.get(); }
      B const* observe_B() const { return b.get(); }
    
      std::shared_ptr<B const> get_shared_B() const {
        return {shared_from_this(), observe_B()};
      }
      std::shared_ptr<B> get_shared_B() {
        return {shared_from_this(), observe_B()};
      }
    };
    

    这里我们使用共享指针的“别名构造函数”来返回一个指向非共享对象的共享指针。它正是为此目的而设计的。我们使用A 的共享生命周期语义,但将其应用于B*

    C 中,我们只需存储vector&lt;weak_ptr&gt;

    struct C {
      std::vector<std::weak_ptr<B>> m_Bs;
    };
    

    现在,当A 消失时,weak_ptr 对“包含”B 的最后一个强引用丢失。当你.lock()它时,它现在失败了。

    struct C {
      std::vector<std::weak_ptr<B>> m_Bs;
      void tidy_Bs() {
        auto it = std::remove_if( begin(m_Bs), end(m_Bs), [](auto&& x)->bool{return !x.lock();});
        m_Bs.erase(it, end(m_Bs));
      }
    };
    

    tidy_Bs 删除所有“悬空”weak_ptrs 到 B 中的 m_Bs

    要迭代,我通常会这样做:

    struct C {
      std::vector<std::weak_ptr<B>> m_Bs;
      void tidy_Bs() {
        auto it = std::remove_if( begin(m_Bs), end(m_Bs), [](auto&& x)->bool{return !x.lock();});
        m_Bs.erase(it, end(m_Bs));
      }
    
      template<class F>
      void foreach_B(F&& f) {
        tidy_Bs();
        auto tmp = m_Bs;
        for (auto ptr:m_Bs)
          if (auto locked = ptr.lock())
            f(*locked);
      }
    };
    

    它为m_Bs 向量中每个仍然存在的Bs 传递fB&amp;。当它在那里时,它会清理死者。

    我复制向量是因为在迭代时,有人可以去更改 m_Bs 的内容,并且为了稳健,我不能在发生这种情况时迭代 m_Bs

    整个技术可以在 A 不被 shared_ptr 管理的情况下完成;但是B 必须由shared_ptr 管理。

    请注意,如果 C 当前在 B 包含在 A 中的 .lock() 上,则“通常”会导致 A 被销毁的操作实际上可能不会执行此操作。实际上没有办法避免这种情况,除了让C 崩溃。

    【讨论】:

      猜你喜欢
      • 2012-09-10
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-02
      • 2019-12-20
      相关资源
      最近更新 更多