Вот контекст. Я работаю над приложением Qt, которое рисует что-то. В какой-то момент логика может потребовать выполнить вращение или перемещение сцены для отрисовки конкретных объектов, учитывая, что это вращение и/или перемещение обязательно должно быть сброшено перед рисованием других объектов.
Недавно я обнаружил ошибку, из-за которой объекты рисовались неправильно. В итоге я нашел причину в фрагменте кода, который выглядит следующим образом:
void example(const QString &text) {
... // A few lines of code.
this->setRotation(angle);
... // A few lines of code.
if (text.trimmed().length() == 0) {
return;
}
... // A few more lines of code.
this->resetRotation();
}
Ошибка заключалась в том, что я забыл поставить resetRotation
перед return
в блоке if
.
Каким идиоматическим способом в современном C++ можно было бы предотвратить подобные случаи?
На ум приходит RAII-подобный шаблон, и я могу представить, что setRotation
возвращает объект, который автоматически вызывает resetRotation
в своем деструкторе — точно так же, как std::unique_ptr
делает с указателем, который он держит. Однако это не кажется особенно ясным с точки зрения вызывающего setRotation
. Кто-то может быть вынужден создать переменную для хранения фактического результата setRotation
, если используется атрибут [[nodiscard]]
, но наличие переменной, которая не имеет никакой другой цели, кроме очистки (и которая не будет вызываться), я полагаю, будет выглядеть странно.
Это так?
Каковы альтернативы?
По сути, вам нужно что-то вроде этого: en.cppreference.com/w/cpp/experimental/scope_exit
«Оригинальный» способ — написать класс, деструктор которого выполняет действие, но для этого требуется довольно много шаблонного кода. См. stackoverflow.com/questions/50182244/… для получения более кратких вариантов. В своих проектах я составляю класс, по функционалу аналогичный предложенному experimental/scope_exit
std::experimental::scope_exit не работает. Вместо этого посетите ricab.github.io/scope_guard.
Не делайте возврат функции объектом RAII, просто создайте защиту области в строке после нее, и если вам не нужны причудливые функции защиты области, вы можете написать в 8 строках простую защиту области, которая создает небольшой код. , что почти идентично фактическому написанию функции на каждом пути возврата.
Примечание. Требование наличия пары set+reset часто указывает на то, что изменяемое свойство на самом деле не принадлежит тому месту, где оно находится в данный момент. Рассмотрите возможность передачи его в качестве аргумента в цепочке вызовов, чтобы внести изменения, изменить копию; тогда «перезагрузка» происходит автоматически (путем выбрасывания копии).
Вместо изменения семантики resetRotation
, что может удивить некоторых пользователей, вы можете добавить оболочку RAII, что сделает его очень понятным.
Что-то вроде:
template <typename T>
class AutoRotationReseter {
public:
AutoRotationReseter(T& obj, double angle) : m_obj(obj) {
m_obj.setRotation(angle);
}
~AutoRotationReseter() {
m_obj.resetRotation();
}
// Disable copy/move/assignment:
AutoRotationReseter(AutoRotationReseter const &) = delete;
AutoRotationReseter(AutoRotationReseter &&) = delete;
AutoRotationReseter& operator=(AutoRotationReseter const &) = delete;
AutoRotationReseter& operator=(AutoRotationReseter &&) = delete;
private:
T & m_obj;
};
А затем используйте его, например. это (после опубликованного вами кода):
{
AutoRotationReseter reseter(*this, angle);
// ...
} // scope ends => AutoRotationReseter will reset rotation
Примечания:
Чтобы соблюдать правило 0/3/5, операторы копирования/перемещения/присваивания удалены (их использование в любом случае не подходит для такого RAII-варппера).
Я написал его как шаблон, чтобы он был более общим, а также потому, что я не знал вашего реального класса.
Если он вам нужен только для определенного класса, вы можете удалить шаблон и просто заменить T
своим фактическим классом.
Что касается любого объекта RAII, используйте правило 3/5: в конкретном случае копирование/присваивание выполняет свою работу.
@ Jarod42 Jarod42 хорошая мысль - обновлено.
Именно для этого и нужен RAII. Так что да, небольшая оболочка RAII сделает именно то, что вы хотите. Если это поможет, вы можете (временно) рассмотреть возможность установки ротации как получения ресурса, который необходимо освободить.