使用智能指针

时间:2015-05-08 21:14:19

标签: c++ c++11 c++14

我正在创建多个索引(即使用不同的键)到大量对象中。对象可以更改,集合可以缩小和增长。到目前为止我的想法:

保留多个指向对象的指针集合。 使用set而不是map来更好地封装。 使用unordered_set可以很好地扩展大型数据集。 理想情况下,指针应该是某种形式的智能指针。

我可以很容易地使用unique_ptrs的主集合开始,它管理所有分配,以及使用“原始”指针的二级索引(我暂时省略了支持函数,但请注意索引是一个multiset作为其密钥在整个集合中不是唯一的):

typedef boost::unordered_set< boost::unique_ptr<MyObject>,myobject_hash,myobjects_equal > MyObjects;
typedef boost::unordered_multiset<const MyObject*,myobject_index2_hash,myobject_index2_equal > MyObjectsIndex2;

用法很简单:

MyObjects my_objects;
MyObjectsIndex2 my_objects_index2;

auto it_mo = my_objects.insert(
    boost::unique_ptr<MyObject>(
        new MyObject(...)
    )
);
const MyObject* p_mo = it_mo.first->get();
my_objects_index2.insert(p_mo);

我正在考虑付出额外的努力来替换索引对原始指针的使用以及对主集合的unique_ptrs的const引用。我不确定,但至少不容易。我想我会问其他人是否已经走过那条路,或者有其他建议。

更新

到目前为止的经验教训:

  1. 数据存储类很酷
  2. reference_wrappers很酷
  3. 带有“key”对象数据存储区成员var的xx_set比xx_map更节省空间。但是......你不能轻易地将unique_ptr用作c ++ 11中的键。 c ++ 14显然可以通过std::set<Key>::find设置更好的功能。有关详细信息,请参阅here。因此,就目前而言,管理原始分配的数据存储似乎比尝试强制使用unique_ptr作为集合键或使用映射增加键空间存储更有意义。
  4. 请记住在对象的生命周期内强制键值为const(使用构造函数中提供的const值)

2 个答案:

答案 0 :(得分:1)

这是一种方式。

std::vector<unique_ptr>保存数据项(以确保在向量调整大小时地址不会改变),然后保存带有reference_wrappers(可复制引用)的容器以生成索引。

可编辑的例子:

#include <map>
#include <vector>
#include <set>
#include <string>
#include <functional>
#include <memory>
#include <iostream>

struct Thing {
    Thing(std::string name, int value)
    : _name { std::move(name) }
    , _value { value }
    {}

    const std::string& name() const {
        return _name;
    }

    void write(std::ostream& os) const {
        os << "{ " << _name << " : " << _value << " }";
    }    
private:
    std::string _name;
    int _value;
};

inline std::ostream& operator<<(std::ostream& os, const Thing& t) {
    t.write(os);
    return os;
}

struct multi_index
{
    using multi_by_name_index = std::multimap<std::string, std::reference_wrapper<Thing>>;

    void add_thing(std::string name, int value) {

        // todo: checks to ensure that indexes won't be violated

        // add a new thing to the main store
        _main_store.emplace_back(new Thing{std::move(name), value});

        // store a reference to it in each index
        auto& new_thing = *(_main_store.back().get());
        _name_index.emplace(new_thing.name(), new_thing);
    }

    using multi_by_name_range = std::pair<multi_by_name_index::const_iterator, multi_by_name_index::const_iterator>;
    multi_by_name_range get_all_by_name(const std::string name) const
    {
        return _name_index.equal_range(name);
    }

private:
    std::vector<std::unique_ptr<Thing>> _main_store;
    std::multimap<std::string, std::reference_wrapper<Thing>> _name_index;
};

using namespace std;

int main()
{
    multi_index mi;

    mi.add_thing("bob", 8);
    mi.add_thing("ann", 4);
    mi.add_thing("bob", 6);

    auto range = mi.get_all_by_name("bob");
    for( ; range.first != range.second ; ++range.first) {
        cout << range.first->second << endl;
    }

   return 0;
}

预期产出:

{ bob : 8 }                                                                                                                             
{ bob : 6 }  

答案 1 :(得分:1)

我认识到你的用例可能与我为我的例子设计的用例不同,没有更多的细节,我赢得了无法匹配(我也认为如果你有很多您可以自己找到解决方案的详细信息。

#include <iostream>
#include <map>
#include <set>
#include <memory>
#include <stdexcept>

using namespace std;

class Thing
{
public:
    Thing() = default;
    Thing(const Thing &other) = default;
    Thing(int i, string p, string d) : id(i), desc(d), part(p) {}

    int    id;
    string desc;
    string part;
};

ostream &operator<<(ostream &out, const Thing &t)
{
    if (&t == NULL) out << "(NULL)"; // don't judge me
    else out << t.id << ": " << t.part << " (" << t.desc << ")";
}

class Datastore
{
public:
    Datastore() = default;
    shared_ptr<const Thing> Add(const Thing &t)
    {
        if (!(index_bydesc.find(t.desc) == index_bydesc.end() &&
              index_bypart.find(t.part) == index_bypart.end() &&
              index_byid.find(t.id) == index_byid.end()))
            throw runtime_error("Non-unique insert");
        shared_ptr<const Thing> newt = make_shared<const Thing>(t);
        weak_ptr<const Thing> weak = weak_ptr<const Thing>(newt);
        index_bydesc[newt->desc] = weak;
        index_bypart[newt->part] = weak;
        index_byid[newt->id] = weak;
        store.insert(newt);
        return newt;
    }

    void Remove(const Thing &t)
    {
        shared_ptr<const Thing> p = FindBy_Desc(t.desc);
        store.erase(p);
        index_bydesc.erase(p->desc);
        index_bypart.erase(p->part);
        index_byid.erase(p->id);
    }

    shared_ptr<const Thing> FindBy_Desc(string desc)
    {
        map<string, weak_ptr<const Thing> >::iterator iter = index_bydesc.find(desc);
        if (iter == index_bydesc.end()) return shared_ptr<const Thing>();
        return iter->second.lock();
    }

    // index accessors for part and quantity omitted

private:
    std::set<shared_ptr<const Thing> > store;

    std::map<string, weak_ptr<const Thing> > index_bydesc;
    std::map<string, weak_ptr<const Thing> > index_bypart;
    std::map<int, weak_ptr<const Thing> > index_byid;
};

int main() {
    Datastore d;
    d.Add(Thing(1, "TRNS-A", "Automatic transmission"));
    d.Add(Thing(2, "SPKPLG", "Spark plugs"));
    d.Add(Thing(3, "HOSE-S", "Small hoses"));
    d.Add(Thing(4, "HOSE-L", "Large hoses"));
    d.Add(Thing(5, "BATT-P", "Primary battery (14.5v nominal)"));
    d.Add(Thing(6, "BATT-S", "Secondary batteries (1.5v nominal)"));
    d.Add(Thing(7, "CRKSFT", "Crank shaft"));
    d.Add(Thing(8, "REAC-F", "Fusion reactor power source"));

    cout << *d.FindBy_Desc("Crank shaft") << endl;
    d.Remove(*d.FindBy_Desc("Crank shaft"));
    cout << *d.FindBy_Desc("Crank shaft") << endl;
    return 0;
}

缺点:

  1. 存储结构是只读的。这是一个必要的缺点,因为如果在数据存储区中修改对象的索引字段,索引将变得过时。要修改对象,请将其删除,然后重新添加另一个对象。
  2. 所有字段必须是唯一的。这很容易更改,但您需要将包含list<Thing>的地图保留为非唯一字段的索引,而不仅仅是包含Thing的地图。
  3. 与使用std::map相关的性能问题。 std::unordered_map是对大型数据结构具有更好(常量摊销)访问时间的替代方法(与std::unordered_set相同)。
  4. 偏差:

    1. 鉴于你在这里有一个明确的键值关系,我认为你用地图而不是套装会更好。
    2. 为了解决与引用计数相关的性能问题,如果您始终谨慎维护内部一致性,则可以放弃所有原始指针的智能指针,并通过引用返回值,您可以通过以下方式实现进一步的改进:填充它时使用不安全的对象所有权语义(即,将指针传递给数据存储区随后拥有的堆对象)。更复杂,但最终副本更少,运行时开销更少。