Проверить, содержит ли контейнер shared_ptr указатель? - PullRequest
3 голосов
/ 08 ноября 2011

Использование шаблона Observer.У меня есть класс, который называется Monitor, например, который контролирует набор объектов.Класс является Обозревателем, и каждый объект в его коллекции является Предметом.В настоящее время коллекция реализована в виде std :: list of shared_ptr.В методе Update класса Monitor я хочу проверить, идет ли обновление от одного из объектов в его коллекции.

std::list<SomeSharedPointer> items_;
...
void Monitor::Update(Subject *subject)
{
    if(subject == something_)
    {
        DoSomething();
    }
    else if
    ??
    // if subject is one of the objects in our collection then do something..

}

Предметом здесь является необработанный указатель, а моей коллекцией является список shared_ptr.Как я могу эффективно проверить, является ли входящий объект одним из объектов в моей коллекции?

(Обратите внимание, что мой компилятор, msvc, поддерживает лямбды, если есть алгоритмическое решение, требующее его)

ОБНОВЛЕНИЕ

Я должен добавить, что я понимаю, что могу использовать цикл for над контейнером, но мне интересно, есть ли более причудливый способ.

ОБНОВЛЕНИЕ2

SomeSharedPointer - это typedef для std::shared_ptr<SomeType>, где SomeType происходит от абстрактного класса Subject (стандартная реализация шаблона Observer).SomeType в какой-то момент вызовет Notify(), что вызовет метод Update() для каждого наблюдателя.

Ответы [ 4 ]

5 голосов
/ 08 ноября 2011
auto i = std::find_if(items_.begin(), items_.end(), 
    [=](const SomeSharedPointer& x) { return x.get() == subject; });

if (i != c.end())
{ 
    // Object found, and i is an iterator pointing to it
}

Небольшой вспомогательный метод может сделать это более читабельным:

typedef std::list<SomeSharedPtr> ObserverCollection;

// You can also add a const version if needed
ObserverCollection::iterator find_observer(Subject* s)
{
    return std::find_if(items_.begin(), items_.end(), 
        [=](const SomeSharedPointer& x) { return x.get() == s; });
}

Затем вы можете использовать его следующим образом, если вам нужен итератор

auto i = find_observer(subject);
if (i != items_.end())
{
    // Object found
}

или просто такесли вы этого не сделаете:

if (find_observer(subject) != items_.end())
{
    ...
}
2 голосов
/ 08 ноября 2011

Если у вас нет поддержки auto в C ++ 11, объявите итератор старомодным способом

for (auto iter = items_.begin(); iter != items_.end(); ++iter)
{
     if (subject == iter->get())
     {
         .. do stuff ..
     }
}

Общий указатель имеет функцию .get (), которая возвращает указатель.

0 голосов
/ 12 декабря 2018

Если возможно, вы можете подумать о том, чтобы поменять контейнер на что-то, что улучшит поведение при поиске.Например, вы можете использовать std::set.Это стоит больше за вставку, но быстрее за поиск.Или std::unordered_set.И вставка, и поиск выполняются быстро, но итерация, вероятно, медленнее.Чтобы добиться правильного сравнения, вы можете создать вспомогательный класс, чтобы разрешить преобразование вашего необработанного указателя в общий указатель, который имеет безоперационное удаление.

template <typename T>
struct unshared_ptr {
    std::shared_ptr<T> p_;
    unshared_ptr (T *p) : p_(p, [](...){}) {}
    operator const std::shared_ptr<T> & () const { return p_; }
    operator T * () const { return p_.get(); }
};

Если ваш контейнер поддерживает метод find, то:

typedef unshared_ptr<SomeType> unshared_some;
if (items_.end() != items_.find(unshared_some(subject))) {
    DoSomething();
}

Попробуйте онлайн!

Если вы придерживаетесь std::list, вы можете злоупотребить методом remove_if, передавв предикате, который всегда возвращает false, но выполняет тест на соответствие.

bool matched = false;
auto pred = [subject, &matched](SomeSharedPtr &v) -> bool {
    if (!matched && v.get() == subject) {
        matched = true;
    }
    return false;
};
items_.remove_if(pred);
if (matched) {
    DoSomething();
} //...
0 голосов
/ 08 ноября 2011

Поскольку вы сказали, что наблюдателю необходимо принять решение на основе состояния элементов, которые он отслеживает, вам следует добавить метод к базовому классу (Subject в вашем вопросе), который возвращает перечисление, определяющее состояние элемента , Затем в зависимости от состояния добавьте переключатель в метод обновления:

enum State{ STATE_1, STATE_2 };

void Monitor::Update(Subject *subject)
{
    switch( subject->getState() )
    {
      case STATE_1:
         // do something 1
         break;
      case STATE_2:
         // do something 2
         break;
      default:
         //error
    }
}
...