通过std :: weak_ptr删除对象

时间:2017-05-04 10:56:23

标签: c++ c++11 boost-smart-ptr

我有一个带有数千个对象的std :: vector,存储为shared_ptr。由于该对象具有许多可用于搜索的属性,因此我使用weak_ptr在std :: map和std :: multimap上为此std :: vector维护多个索引。

std::vector<std::shared_ptr<Object>> Objects;
std::map<int,std::weak_ptr<Object>> IndexByEmployeeId;
std::multimap<std::string,std::weak_ptr<Object>> IndexByName;

由于地图和多图是平衡二叉树,因此搜索/修改速度非常快。但是,我对删除有点不满。我想通过其中一个索引查找对象后删除。锁定weak_ptr给了我shared_ptr,但它不允许我销毁向量上的对象。有没有办法删除矢量上的原始对象?

3 个答案:

答案 0 :(得分:2)

这可以是std::setstd::vector更合适的用例。 std::set保证以对数时间查找,插入和删除。因此,您可以在一个地图对象中按索引查找对象,然后在具有log(N)性能的任何容器中将其删除。

如果插入/删除操作代表了应用程序中的性能瓶颈,我会建议这种方法。

顺便提一下,请仔细考虑shared_ptr的实际需求,因为shared_ptr会带来一定的性能开销,而不是unique_ptr。您的所有者容器可以使用unique_ptr,而各种地图只能使用原始指针。

答案 1 :(得分:2)

您发布的内容似乎表明您的数据结构不适合您想要做的事情。

  1. shared_ptr只应用于表达共享所有权。但是,您发布的代码以及删除指向对象的愿望表明Objects实际上是其对象的唯一所有者。
  2. 您的vector<shared_ptr<object>>似乎仅用作数据存储容器(按ID或名称搜索所需的功能在其他地方实现),但从vector删除元素通常很昂贵,因此您可以更好地使用其他类型的容器。
  3. 如果您的对象没有其他shared_ptr,那么您的设计很差。在这种情况下,你不应该使用任何智能指针,只需container<object>并映射到那个。例如像这样的东西(未测试甚至没有编译):

    struct data_t { std::string name; /* more data */ };
    using objt_map = std::map<std::size_t,data_t>;
    using object   = objt_map::value_type;    // pair<const size_t,data_t>
    using obj_it   = objt_map::iterator;
    using name_map = std::multimap<std::string, obj_it>;
    objt_map Objects;
    name_map NameMap;
    
    std::forward_list<obj_it> find_by_name(std::string const&name) const
    {
        auto range = NameMap.equal_range(name);
        std::forward_list<obj_it> result;
        for(auto it=range.first; it!=range.second; ++it)
            result.push_front(it->second);
        return result;
    }
    
    std::forward_list<obj_it> find_by_id(std::size_t id) const
    {
        auto it = Objects.find(name);
        return {it == Objects.end()? 0:1, it};
    }
    
    void insert_object(std::size_t id, data_t const&data)
    {
        auto it = Objects.find(id);
        if(it != Objects.end())
            throw std::runtime_error("id '"+std::to_string(id)+"' already known");
        Objects[id] = data;
        NameMap.emplace(data.name, Objects.find(id));
    }
    
    void delete_object(obj_it it)
    {
        if(it==Objects.end())
            throw std::runtime_error("attempt to delete invalid object");
        auto range = NameMap.equal_range(it->second.name);
        for(auto i=range.first; i!=range.second; ++i)
            if(i->second==it) {
                NameMap.erase(i);
                break;
            }
        Objects.erase(it);
    }
    

    注意std::map的迭代器在插入和删除(其他对象)时仍然有效,这样查找器的返回不会因插入和删除而失效。我使用std::forward_list<obj_it>返回对象以允许返回none或几个。

答案 2 :(得分:1)

所以,这是另一个选项,基于通过移动std::unique_ptr<>导入对象。不幸的是,unique_ptr对于std::set来说并不是有用的键(因为它们是唯一的),除非你有C ++ 14,当set::find()可以使用另一个参数而不是键时(见下文) 。

对于 C ++ 11 方法,必须使用std::map来存储unique_ptr s,这需要将id加倍{{1} }条目:一次在name中,一次作为data_t中的键。这是一个草图。

map

以下是 C ++ 14 解决方案的草图,它避免了地图中struct data_t { const std::size_t id; // changing these would const std::string name; // lead to confusion /* more data */ }; using data_ptr = std::unique_ptr<data_t>; using data_map = std::map<std::size_t,data_ptr>; using obj_it = data_map::iterator; using name_map = std::multimap<std::string,obj_it>; data_map DataSet; name_map NameMap; std::vector<data_t*> find_by_name(std::string const&name) const { auto range = NameMap.equal_range(name); std::vector<data_t*> result; result.reserve(std::distance(range.first,range.second)); for(auto it=range.first; it!=range.second; ++it) result.push_back(it->second->get()); return result; } data_t* find_by_id(std::size_t id) const { auto it = DataSet.find(id); return it == DataSet.end()? nullptr : it->second.get(); } // transfer ownership here void insert_object(data_ptr&&ptr) { const auto id = ptr->id; if(DataSet.count(id)) throw std::runtime_error("id '"+std::to_string(id)+"' already known"); auto itb = DataSet.emplace(id,std::move(ptr)); auto err = itb.second; if(!err) err = NameMap.emplace(itb.first->name,itb.first).second; if(err) throw std::runtime_error("couldn't insert id "+std::to_string(id)); } // remove object given an observing pointer; invalidates ptr void delete_object(data_t*ptr) { if(ptr==nullptr) return; // issue warning or throw ? auto it = DataSet.find(ptr->id); if(it==DataSet.end()) throw std::runtime_error("attempt to delete an unknown object"); auto range = NameMap.equal_range(it->second->name); for(auto i=range.first; i!=range.second; ++i) if(i->second==it) { NameMap.erase(i); break; } DataSet.erase(it); } id数据的重复,但需要/假设{ {1}}和name是不变的。

data_t::id

这里可能存在一些错误,尤其是解除引用各种迭代器和指针类型的错误(尽管一旦编译它们应该没问题)。