【问题标题】:How to get rid of weak_ptrs in a container如何摆脱容器中的weak_ptrs
【发布时间】:2013-10-04 06:36:48
【问题描述】:

我有一个类将weak_ptr 存储在容器中,如果weak_ptr 未过期,稍后会执行一些操作:

class Example
{
public:
    void fill(std::shared_ptr<int> thing)
    {
        member.push_back(thing);
    }
    void dosomething() const
    {
        for (const auto& i : member)
            if (!i.expired())
                ;// do something. the weak_ptr will not be locked
    }
private:
    std::vector<std::weak_ptr<int>> member;
};

如果Example 是一个永远存在的对象并且fill 被定期使用,则向量会连续为元素分配内存,但它们在过期后永远不会被删除。

是否有任何自动的 C++ 方法来摆脱容器中过期的weak_ptrs,或者是否有更好的方法来存储可变数量的它们?

我幼稚的方法是每次调用fill 时遍历容器并删除所有过期的weak_ptrs。在Example 在容器中有很多元素并且经常调用填充的情况下,这似乎非常低效。

【问题讨论】:

  • 您可以为原始shared_ptr 使用自定义删除器,该删除器引用Example 对象并删除它将要删除的指针(如果它在其中)。这样,您甚至不需要存储 weak_ptr
  • @Xeo 也许我把我的例子简化得太多了。实际上,容器是一个将weak_ptr 与某物相关联的映射。 Example 类需要知道哪些对象何时过期以及哪些对象过期。
  • 天真的方法可能没有你想的那么糟糕。如果您的指针经常过期,并且在添加新指针之前检查并清除了fill 中的指针,您可能会阻止向量重新分配,因为您刚刚为要添加的指针腾出了空间。编辑:刚刚看到你的评论;如果容器是地图,我所说的不适用。此外,无论容器是什么类型,@Xeo 所说的都有效。客户删除器只需要能够通过容器查看它是否包含即将被删除的指针的副本。
  • 将第二行 if(member.size()%N==0)cleanup(); 添加到 fill() 并且仅在每 N 次调用时清理一次?
  • @typ1232:你打算用过期的物品做什么?此外,删除器可以准确地告诉 Example 对象 - 甚至准确地告诉对象已过期!

标签: c++ c++11 weak-ptr


【解决方案1】:

由于您澄清您实际上使用的是std::map 而不是std::vector,因此在doSomething() 中即时删除过期元素可能是最简单的。从基于范围的 for 循环切换回基于普通迭代器的设计:

void dosomething() const
{
    auto i = member.begin();
    while( i != member.end() ) {
      if( i->expired() ) { i = member.erase( i ); continue; }
      ;// do something. the weak_ptr will not be locked
      ++i;
    }
}

【讨论】:

    【解决方案2】:

    shared_ptr&lt;int&gt; 必须是 shared_ptr&lt;int&gt; 吗?

    shared_ptr&lt;IntWrapper&gt; 怎么样?

    #include <iostream>
    #include <forward_list>
    using namespace std;
    
    class IntWrapper {
    public:
        int i;
    
        static forward_list<IntWrapper*>& all() {
            static forward_list<IntWrapper*> intWrappers;
            return intWrappers;
        }
        IntWrapper(int i) : i(i)  {
            all().push_front(this);
        }
        ~IntWrapper() {
            all().remove(this);
        }
    };
    
    void DoSomething() {
        for(auto iw : IntWrapper::all()) {
            cout << iw->i << endl;
        }
    }
    
    int main(int argc, char *argv[]) {
        shared_ptr<IntWrapper> a = make_shared<IntWrapper>(1);
        shared_ptr<IntWrapper> b = make_shared<IntWrapper>(2);
        shared_ptr<IntWrapper> c = make_shared<IntWrapper>(3);
        DoSomething();
        return 0;
    }
    

    【讨论】:

    • 谢谢。这完全适用于我的情况,我真的很喜欢!
    • 如果你使用shared_ptr别名(codesynthesis.com/~boris/blog/2012/04/25/…),它实际上可能是shared_ptr&lt;int&gt;
    • @typ1232 请注意,all().remove(this) 按值删除,因此具有与容器大小成线性关系的复杂性——这可能效率很低。如果效率很重要,您可能需要考虑其他解决方案,例如我发布的解决方案。
    • 确实如此。但除非此代码用于手表,或者操作处理 10,000 多个对象,否则不太可能成为问题 =)
    【解决方案3】:

    我宁愿为 shared_ptr 使用自定义删除器。但这意味着这里要更改 Example 类的接口。使用自定义删除器的优点是无需检查集合中的过期对象。该集合由自定义删除器直接维护。

    快速实施:

    #include <memory>
    #include <iostream>
    #include <set>
    
    template <typename Container>
    // requires Container to be an associative container type with key type
    // a raw pointer type
    class Deleter {
        Container* c;
    public:
        Deleter(Container& c) : c(&c) {}
        using key_type = typename Container::key_type;
        void operator()(key_type ptr) {
            c->erase(ptr);
            delete ptr;
        }
    };
    
    class Example {
    public:
        // cannot change the custom deleter of an existing shared_ptr
        // so i changed the interface here to take a unique_ptr instead
        std::shared_ptr<int> fill(std::unique_ptr<int> thing) {
            std::shared_ptr<int> managed_thing(thing.release(), Deleter<containter_type>(member));
            member.insert(managed_thing.get());
            return managed_thing;
        }
    
        void dosomething() const {
            // we don't need to check for expired pointers
            for (const auto & i : member)
                std::cout << *i << ", ";
    
            std::cout << std::endl;
        }
    
        using containter_type =  std::set<int*>;
    private:
        containter_type member;
    };
    
    int main()
    {
        Example example;
        auto one = example.fill(std::unique_ptr<int>(new int(1)));
        auto two = example.fill(std::unique_ptr<int>(new int(2)));
        auto three = example.fill(std::unique_ptr<int>(new int(3)));
        example.dosomething();
        three.reset();
        example.dosomething();
    }
    

    【讨论】:

    • 以这种方式构造 shared_ptr 会导致 2 个分配,这很可惜
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-28
    相关资源
    最近更新 更多