2016-12-12 5 views
0

Имея набор указателей на уникальные объекты типа A, я хочу создать и вставки новый элемент в наборе, но только если нет такого элемент уже. Проблема заключается в том, что стандартные компараторы работают с существующими объектами, а один еще не существует. Это означает, что я не могу определить, находится ли объект в наборе без создания объекта (или без повторного набора всего набора вручную).Создать объект, если он еще не в наборе

Одним из очевидных решений было бы заменить набор на карту с ключами, содержащими часть A, что делает объекты типа A уникальными, но это почти удваивает использование памяти. Это неприемлемо для меня.

Любые идеи?

ОБНОВЛЕНИЕ Чтобы быть более конкретным, здесь приведен упрощенный пример:

class Base { /* members */ }; 

class Derived : public Base { 
    static std::set<Derived *> cache; 
    std::vector<Object *> v; 
public: 
    static Derived *Create(const std::vector<Object *> &v); 
    /* other members */ 
}; 

Derived *Derived::Create(const std::vector<Object *> &v) { 
    /* Here I need to create and insert a new object of type Derived 
    into the `Derived::cache`, but only if there is no such object 
    in the set yet. Objects are uniqued by contents of `v`. */ 
} 
+4

Посмотрите на ['std :: find_if()'] (http://en.cppreference.com/w/cpp/algorithm/find) для поиска существующего объекта. Если не найден, создайте + вставить новый объект. –

+3

'стандартные компараторы работают с существующими объектами, а один еще не существует. Извините, я не понимаю. Почему вы не можете реализовать 'operator <' снова? –

+0

Как @BryanChen сказал - просто определите свой собственный оператор сравнения. – pstrjds

ответ

1

std::find_if принимает произвольный тип поддерживается в C++ 14:

#include <set> 
#include <tuple> 
#include <memory> 

// 
// the concept of an Identity - some signature that identifies an object 
// uniquely 
struct Identity 
{ 
    Identity(int v) : v_(v) {} 

    auto as_tuple() const { return std::tie(v_); } 

    private: 
    int v_; 
}; 
bool operator<(Identity const& l, Identity const& r) 
{ 
    return l.as_tuple() < r.as_tuple(); 
} 

// 
// some object that has an identity 
// 
struct Thing 
{ 
    Thing(Identity i) : ident_(std::move(i)) {} 

    const Identity& identity() const { return ident_; } 

private: 
    Identity ident_; 
}; 

struct LessThing 
{ 
    struct is_transparent {}; 

    bool operator()(const std::unique_ptr<const Thing>& l, const Identity& r) const 
    { 
    return l->identity() < r; 
    } 

    bool operator()(const Identity& l, const std::unique_ptr<const Thing>& r) const 
    { 
    return l < r->identity(); 
    } 

    bool operator()(const std::unique_ptr<const Thing>& l, const std::unique_ptr<const Thing>& r) const 
    { 
    return l->identity() < r->identity(); 
    } 

}; 


int main() 
{ 
    std::set<std::unique_ptr<const Thing>, LessThing> myset; 
    myset.insert(std::make_unique<Thing>(Identity(5))); 
    myset.insert(std::make_unique<Thing>(Identity(6))); 

    if (myset.find(Identity(7)) == myset.end()) 
    { 
     myset.emplace(std::make_unique<Thing>(Identity(7))); 
    } 
} 

Примечание 1: определение введите is_transparent в функции сравнения, чтобы включить это поведение.

Примечание2: Помните, что элементы набора являются константами. Если сравнение ключей зависит от объекта, на который указывает указатель в наборе, эти данные (или, по крайней мере, их идентификационная часть) должны быть неизменными.

+0

Совершенно, за исключением одной незначительной вещи: идентификационная часть объекта должна быть перемещена в элемент идентификации (чтобы избежать нежелательного чрезмерного использования памяти). Вам нужно медитировать на этом. –

+0

Это просто: в этом случае я создал два объекта Identifier, но вы можете сравнить их с одним, а затем переместить его. –

+0

Я имел в виду, что идентификационные элементы исходного объекта ('v' класса' Derived' в моем случае) имеют (не путать с std :: move) во вновь введенном идентификационном элементе ('ident_' в вашем коде). Но я считаю, что есть способ не делать этого, создавая 'Identity' на лету в функции' identity' member. Вот почему мне нужно медитировать. –

0

Вам необходимо позвонить set :: count.

count затем вызовет оператор «<» на объект, который вы передаете, а также объекты в наборе. Поэтому вы должны построить объект.

Оператор '<' не имеет доступа к каждому полю. Вероятно. Если вы абсолютно должны сравнивать на каждом поле, тогда нет другого решения, кроме создания объекта запроса. Но, скорее всего, у объекта есть Id или аналогичное поле, которое вы используете для быстрого сравнения. Для этого вам нужен специальный конструктор.

class MyClass 
{ 
    int id; 
    std::vector<int> expensive_member; 
    friend bool operator < (MyClass const &, MyClass const &); 

    public: 
    MyClass(std::vector<int> const &expensivetosetup, int id); 
    MyClass(int id); 
    void member() { assert(expensive_member.empty() == false);} 
} 

bool operator < (MyClass const &a, MyClass const &b) 
{ 
    return a.id < b.id; 
} 

Настроить так. Тогда специальный конструктор используется только для создания дешевого объекта запроса. Для некоторой дополнительной безопасности, мы завершаем утверждение, вокруг других членов, поэтому частично построенные объекты не могут быть использованы для чего-либо еще.

Следует подчеркнуть, что это плохой дизайн. Реальный ответ заключается в том, чтобы использовать пару ключ/значение и взять ключ из объекта, если это требует . Но иногда другие давления создают плохие проекты, и это ваш ответ.

+0

Я не могу вывести ключ из объекта, потому что данные, которые были ключом, являются семантической частью объекта. С другой стороны, поскольку эти данные однозначно идентифицируют объект, естественно использовать его в качестве ключа, когда объект этого типа хранится в контейнере типа 'std :: set'. Похоже, комитет РГ21 имел это в виду, когда они изобрели C++ 14. Спасибо и загляните в принятый ответ за подробностями. –

Смежные вопросы