Существует хороший принцип проектирования, согласно которому у каждого класса должна быть одна цель. Для выполнения этой задачи должен существовать «объект задачи». Когда вы начинаете добавлять дополнительные обязанности, вы, как правило, оказываются в беспорядке. Беспорядки могут включать в себя необходимость не забывать вызывать определенный метод после выполнения основной задачи или необходимость не использовать хакерский обходной путь, чтобы сохранить объект живым. Беспорядки часто являются признаком неадекватной мысли, заложенной в дизайн. Недовольство беспорядком говорит о вашем потенциале для хорошего дизайна.
Давайте вернемся и рассмотрим реальную проблему . В контейнере хранятся объекты задач. Контейнер решает, когда вызывать каждую задачу. Задача должна быть удалена из контейнера перед вызовом следующей задачи (чтобы она больше не вызывалась). Мне кажется, что ответственность за удаление элементов из контейнера должна ложиться на контейнер.
Так что мы заново представим ваш класс без этого беспорядка "SelfDestruct". Ваши объекты задачи существуют для выполнения задачи. Они, вероятно, полиморфные c, поэтому необходим контейнер указателей на объекты задач, а не контейнер объектов задач. Объекты задачи не заботятся о том, как они управляются; это работа для кого-то другого.
class Task {
public:
Task() {}
// Other constructors, the destructor, assignment operators, etc. go here
void DoWork() {
// Stuff is done here.
// The work might involve adding tasks to the queue.
}
};
Теперь сосредоточьтесь на контейнере. Контейнер (точнее, владелец контейнера) отвечает за добавление и удаление элементов. Так сделай это. Вы, кажется, предпочитаете удалить элемент, прежде чем вызывать его. Это кажется хорошей идеей для меня, но не пытайтесь отложить удаление задачи. Вместо этого используйте вспомогательную функцию, сохраняя эту логику c на уровне абстракции владельца контейнера.
// Extract the first element of `ownerSet`. That is, remove it and return it.
// ASSUMES: `ownerSet` is not empty
std::shared_ptr<Task> extract(std::unordered_set<std::shared_ptr<Task>>& ownerSet)
{
auto begin = ownerSet.begin();
std::shared_ptr<Task> first{*begin};
ownerSet.erase(begin);
return first;
}
TEST_CASE("Removal from the container should not cause undefined behavior") {
std::unordered_set<std::shared_ptr<Task>> ownerSet;
for (int i = 0; i < 100; ++i)
ownerSet.emplace(std::make_shared<Task>());
while (!ownerSet.empty()) {
// The temporary returned by extract() will live until the semicolon,
// so it will (barely) outlive the call to DoWork().
extract(ownerSet)->DoWork();
// This is equivalent to:
//auto todo{extract(ownerSet)};
//todo->DoWork();
}
}
С одной точки зрения, это почти тривиальное изменение по сравнению с вашим подходом, так как все, что я сделал, это сдвиг ответственность от объекта задачи перед владельцем контейнера. Но с этим сдвигом беспорядок исчезает. Те же шаги выполняются, но они имеют смысл и почти вынуждены, когда перемещаются в более подходящий контекст. Чистый дизайн ведет к чистой реализации.