Here's the context. I'm working on a Qt application that draws stuff. At some point, the logic may require to perform a rotation or a translation of the scene in order to draw specific objects, given that this rotation and/or translation should necessarily be reset before drawing other objects.
Recently, I found a bug where the objects weren't drawn correctly. I ended up by finding the cause, in the piece of code which looks like this:
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();
}
The error was that I forgot to call resetRotation
before the return
in the if
block.
What would be an idiomatic way, in modern C++, to prevent such cases from happening?
RAII-like pattern comes into mind, and I could imagine for setRotation
to return an object which would automatically call resetRotation
in its destructor—just like std::unique_ptr
does with the pointer it holds. This, however, doesn't seem particularly clear from the perspective of the caller of setRotation
. One may be forced to create a variable to store the actual result of setRotation
if [[nodiscard]]
attribute is used, but having a variable which has no other purpose than the cleanup (and which won't be called) would, I suppose, look weird.
Does it?
What are the alternatives?
Instead of changing the semantics of resetRotation
which might surprise some users, you can add an RAII wrapper which makes it very clear.
Something like:
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;
};
And then use it like e.g. this (following the code you posted):
{
AutoRotationReseter reseter(*this, angle);
// ...
} // scope ends => AutoRotationReseter will reset rotation
Notes:
In order to respect the rule of 0/3/5 the copy/move/assignment operators are deleted (using them does not fit such a RAII warpper anyway).
I wrote it as a template to be more generic and also because I did not know your actual class.
If you need it for a specific class only, you can drop the template and simply replace T
with your actual class.