智能指针是否能够从容器中删除对其对象的其他引用?

Are smart pointers capable of removing other references to its object from containers?

我想实现这个:

不同智能指针的组合是否可行?

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

对象 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(我想避免)。

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(); }
};

我们将使其成为逻辑常量。对于我们有实际 B 的情况,我们只做 & 而不是 .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()};
  }
};

这里我们使用"aliasing constructor"的共享指针指向return一个指向非共享对象的共享指针。它正是为此目的而设计的。我们使用 A 的共享生命周期语义,但将其应用于 B*.

C中我们简单的存储了一个vector<weak_ptr>.

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

现在,当 A 消失时,weak_ptr 到 "contained" 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 删除 m_Bs.

中的所有 "dangling" weak_ptrB

为了迭代,我通常会这样做:

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 向量中每个仍然存在的 B 传递 f 一个 B&。当它在那里时,它会清理死者。

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

整个技术无需 Ashared_ptr 管理即可完成;但是 B 必须由 shared_ptr 管理。

请注意,如果 C 当前在 [=19= 上有一个 .lock(),则 "normally" 导致 A 被销毁的操作实际上可能不会执行此操作] 包含在 A 中。实际上没有办法避免这种情况,除了使 C 崩溃。