2017-05-04 61 views
2

我有一個包含數千個對象的std :: vector,存儲爲shared_ptr。由於對象具有許多可用於搜索的屬性,因此我使用weak_ptr維護std :: map上的std :: vector和std :: multimap的多個索引。通過std :: weak_ptr刪除一個對象

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

由於map和multimap是平衡的二叉樹,所以搜索/修改速度非常快。不過,我有點討厭刪除。我想通過其中一個索引查找對象後刪除。鎖定weak_ptr會給我shared_ptr,但它不允許我銷燬向量上的對象。有什麼辦法可以刪除矢量上的原始對象嗎?

+8

爲什麼你甚至想手動刪除一個'shared_ptr'持有的對象?這就違背了使用'shared_ptr'的目的。 – UnholySheep

+4

如何從vector中移除'shared_pr'?這裏沒有魔法 - 從vector中移除'shared_ptr'會調用它的析構函數(除非通過移動來移除)。如果沒有任何其他'shared_ptr'實例持有引用對象的ref-count,'shared_ptr'的析構函數將自動釋放Object的實例的內存。記得在事後正確檢查'std :: weak_ptr :: lock()'的成功,即檢查結果是否爲'nullptr',或者同時移除地圖中的'weak_ptr's。 – thokra

+0

從向量中刪除意味着我必須遍歷向量才能找到並刪除它。這違背了索引的目的。請注意我不想理解smart_ptr/weak_ptr的工作情況。有沒有什麼辦法可以重置()vector上的smart_ptr,而無需迭代它?我只有一個weak_ptr對象。 – Sharath

回答

2

從您發佈的內容看,您的數據結構似乎不適合您的要求。

  1. shared_ptr只應該用來表示共享所有權。但是,您的發佈代碼和您希望刪除指向的對象表明Objects實際上是其對象的唯一所有者。
  2. 您的vector<shared_ptr<object>>似乎只能用作數據存儲容器(通過id或名稱進行搜索的所需功能在別處實現),但從vector中刪除元素通常很貴,因此您最好使用其他類型的容器。

如果沒有其他的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>返回對象以允許返回一個或幾個。

+0

該對象在其他地方創建並以shared_ptr的形式出現在我的面前。一旦對象被存儲,它就擁有它。該對象本身具有成員,一些又是smart_ptr。使用smart_ptr是因爲它很容易跨越功能傳輸對象,同時避免每次都進行復制。我可以檢查使用unique_ptr和move()代替smart_ptr的可行性。 – Sharath

+1

@Sharath如果你的系統擁有它,它聽起來好像不應該被創建爲'shared_ptr'。你有任何控制權?通常,當創建對象時,它們被放置在'unique_ptr'中,因爲這使消費者可以選擇只在需要時將其移動到'shared_ptr'中。 – Galik

+0

我確實可以控制整個系統。但是,該對象是在不同的模塊中創建的,並在存儲之前通過幾個函數。我想避免複製,因爲這些是複雜的對象。 – Sharath

2

這可以是一個使用案例,其中std::set是比std::vector合適的選擇。 std::set保證在對數時間內的查找,插入和刪除。因此,您可以通過您的某個地圖對象中的索引來查找對象,然後在任何具有log(N)性能的容器中將其刪除。

如果插入/刪除操作代表應用程序中的性能瓶頸,我會建議這種方法。

順便說一句,仔細考慮shared_ptr的實際需要,因爲shared_ptr帶有一定的性能開銷,而不是unique_ptr。您的所有者容器可以使用unique_ptr,各種地圖可以簡單地使用原始指針。

+0

通過「索引」查找集合中的對象?真? –

+0

@BoundaryImposition否,查找其他地圖之一。 –

+1

順便說一下,堅持'shared_ptr' /'weak_ptr'的安全性,直到你真的不需要。 –

1

因此,這裏是另一個選項,基於通過移動std::unique_ptr<>來導入對象。不幸的是,unique_ptr對於std::set(因爲它們是唯一的)並不是有用的鍵,除非你有C++ 14,否則當set::find()可以採用另一個參數而不是一個鍵(見下文)時。

對於C++ 11方法,必須使用因此std::map存儲unique_ptr s,這需要加倍idname條目:一旦在data_t,一次作爲在map S]鍵。這是一張草圖。

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

這裏爲C++ 14溶液,這避免了在地圖中的idname數據的重複的草圖,但需要/假定data_t::iddata_t::name是不變的。

struct data_t { 
    const std::size_t id;  // used as key in set & multiset: 
    const std::string name;  // must never be changed 
    /* more data */ 
}; 

using data_ptr = std::unique_ptr<data_t>; 

struct compare_id { 
    using is_transparent = std::size_t; 
    static bool operator(data_ptr const&l, data_ptr const&r) 
    { return l->id < r->id; } 
    static bool operator(data_ptr const&l, std::size_t r) 
    { return l->id < r; } 
    static bool operator(std::size_t l, data_ptr const&r) 
    { return l < r->id; } 
}; 

using data_set = std::set<data_ptr,compare_id>; 
using data_it = data_set::const_iterator; 

struct compare_name { 
    using is_transparent = std::string; 
    static bool operator(data_it l, data_it r) 
    { return (*l)->name < (*r)->name; } 
    static bool operator(data_it l, std::string const&r) 
    { return (*l)->name < r; } 
    static bool operator(std::string const&l, data_it r) 
    { return l < (*r)->name; } 
}; 

using name_set = std::multiset<data_it,compare_name>; 

data_set DataSet; 
name_set NameSet; 

std::vector<data_t*> find_by_name(std::string const&name) const 
{ 
    auto range = NameSet.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)->get()); 
    return result; 
} 

data_t* find_by_id(std::size_t id) const 
{ 
    auto it = DataSet.find(id); 
    return it == DataSet.end()? nullptr : it->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(std::move(ptr)); 
    auto err = itb.second; 
    if(!err) 
     err = NameSet.emplace(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 = NameSet.equal_range(ptr->name); 
    for(auto i=range.first; i!=range.second; ++i) 
     if((*i)==it) { 
      NameSet.erase(i); 
      break; 
     } 
    DataSet.erase(it); 
} 

有可能是在這裏的一些錯誤,特別是與錯誤提領該迭代器的各種指針和類型(儘管一旦它編譯這些應該沒問題)。