Эта проблема дизайна всплывает снова и снова, и у меня все еще нет хорошего решения для нее.Это может оказаться шаблоном дизайна;) Только кажется, что он очень специфичен для C ++ (отсутствие сборки мусора).В любом случае, вот проблема:
У нас есть родительский объект, который хранит ссылки на дочерние объекты.Состояние родителя зависит от (некоторой совокупности) состояний его детей.Чтобы получать уведомления об изменениях состояния своих дочерних элементов, он передает им ссылку на себя.(В другом варианте он передает им обратный вызов, который дочерние элементы могут вызывать для уведомления родителя. Этот обратный вызов является закрытием, которое сохраняет ссылку на родительский элемент.) Приложение является многопоточным.Теперь эта установка представляет собой целое гнездо потенциальных условий гонки и мертвых замков.Чтобы понять почему, вот наивная реализация:
class Parent {
public:
Parent() {
children_["apple"].reset(new Child("apple", this));
children_["peach"].reset(new Child("peach", this));
}
~Parent() {
}
void ChildDone(const string& child) {
cout << "Child is DONE: " << child << endl;
}
private:
map<string, linked_ptr<Child> > children;
};
class Child {
public:
Child(const string& name, Parent* parent)
: name_(name), parent_(parent), done_(false) {}
Foo(int guess) {
if (guess == 42) done_ = true;
parent->ChildDone(name_);
}
private:
const string name_;
Parent* parent_;
bool done_;
};
Потенциальные проблемы:
Во время уничтожения родителя, он должен быть в поиске текущих обратных вызовов от своих потомков.Особенно, если эти обратные вызовы запускаются в отдельном потоке.Если это не так, он может исчезнуть к тому времени, когда вызывается обратный вызов. Если есть блокировки как в родительском, так и в дочернем элементах (очень вероятно, в многопоточном нетривиальном приложении), порядок блокировки становится проблемой: родительский объект вызывает метод для дочернего элемента, который, в свою очередь, испытывает переход состоянияи пытается уведомить родителя: dead-lock. Добавление / удаление дочерних элементов вне конструктора может быть проблемой, если дочерний элемент пытается уведомить родителя от своего деструктора.Родитель должен удерживать блокировку, чтобы изменить карту дочерних элементов, однако дочерний объект пытается выполнить обратный вызов родительского элемента.
Я только поцарапал поверхность, но можно подумать о других потенциальных проблемах.
Я ищу несколько советов о том, как справиться с чистым уничтожением родителя перед лицом потоков, блокировки и динамическое добавление / удаление детей.Если кто-нибудь придумал элегантное решение, которое является надежным при многопоточном развертывании, пожалуйста, поделитесь.Ключевое слово здесь: надежный : легко спроектировать структуру, которая сопровождается некоторыми огромными предостережениями (child никогда не вызывает parent, родительский никогда не вызывает child, нет отдельного потока для обратных вызовов и т. Д.), Задача состоит в том, чтобы поставитькак можно меньше ограничений для программиста.