В настоящее время я реорганизую приложение, в котором классы могут вызывать наблюдателей, если их состояние изменяется. Это означает, что наблюдатели называют всякий раз, когда:Как защитить целостность структуры данных при вызове внешней логики через наблюдателей?
- данных в экземпляре класса изменяет
- новые экземпляры класса создаются
- экземпляры класса удаляются
Именно этот последний случай, который заставляет меня беспокоиться.
Предположим, что мой класс - это книга. Наблюдатели хранятся в классе BookManager (BookManager также хранит список всех книг). Это означает, что мы имеем это:
class Book
{
...
};
class BookManager
{
private:
std::list<Book *> m_books;
std::list<IObserver *> m_observers;
};
Если книга будет удалена (удален из списка и удаляются из памяти), наблюдатели называют:
void BookManager::removeBook (Book *book)
{
m_books.remove(book);
for (auto it=m_observers.cbegin();it!=m_observers.cend();++it) (*it)->onRemove(book *);
delete book;
}
Проблема заключается в том, что я не имею никакого контроля над логика в наблюдателях. Наблюдатели могут доставляться плагином по коду, написанному разработчиками у заказчика.
Таким образом, хотя я могу писать код, как это (и я убедиться в получении следующего в списке в случае, если экземпляр удален):
auto itNext;
for (auto it=m_books.begin();it!=m_books.end();it=itNext)
{
itNext = it:
++itNext;
Book *book = *it;
if (book->getAuthor()==string("Tolkien"))
{
removeBook(book);
}
}
Существует всегда возможность наблюдателя удаления другого книги из списка, а также:
void MyObserver::onRemove (Book *book)
{
if (book->getAuthor()==string("Tolkien"))
{
removeAllBooksFromAuthor("Carl Sagan");
}
}
в этом случае, если список содержит книгу Толкиена, а затем в книге Карла Сагана, петлю, которая удаляет все книги Толкиена, вероятно, врезаться со следующего итератора (itNext) стал недействительным.
Иллюстрированная проблема может также возникать в других ситуациях, но проблема с удалением является самой серьезной, так как она может легко свернуть приложение.
Я мог бы решить эту проблему, убедившись, что в приложении я сначала получаю все экземпляры, которые хочу удалить, поместите их во второй контейнер, затем переверните второй контейнер и удалите экземпляры, но поскольку всегда есть риск того, что наблюдатель явно удалит другие экземпляры, которые уже были в моем списке для удаления, я должен предоставить явным наблюдателям возможность обновить эту вторую копию.
Кроме того, требуется, чтобы весь код приложения составлял копии списков всякий раз, когда они хотят перебирать контейнер при вызове наблюдателей (прямо или косвенно), значительно затрудняет написание кода приложения.
Есть ли [дизайн] шаблонов, которые могут быть использованы для решения этой проблемы? Не рекомендуется использовать метод общего указателя, поскольку я не могу гарантировать, что все приложение использует общие указатели для доступа к экземплярам.
Похоже, ваша проблема глубже: ваши клиенты имеют прямой доступ к итераторам коллекции, и в то же время вы хотите, чтобы в коллекции было сложное поведение, не сохраняющее итераторы. Я бы изолировал API для операций мутаторов, явно заявляя, что любые итераторы, удерживаемые клиентами, становятся недействительными. –