In a visitor context, I need to temporarily set a variable before visiting children, and revert that variable afterward. I'm using the following code, but I'm sure there's a more elegant and proper way to do this:
template <typename TYPE> class TemporaryAssignment {
protected:
TYPE& mVariable;
TYPE mOriginalValue;
public:
TemporaryAssignment(TYPE& inVariable, TYPE inValue)
: mVariable(inVariable), mOriginalValue(inVariable) {
mVariable = inValue;
}
~TemporaryAssignment(void) {
mVariable = mOriginalValue;
}
};
This allows me to write something like the following:
{
...
TemporaryAssignment<string> t(myVariable, myTemporaryValue);
visitChildren();
...
}
// previous value of myVariable is restored
The variable will revert to its previous value when the temporary assignment object goes out of scope. What is a better way to do this?
Looks OK to me except that the destructor can throw, which is bad. swap
with the original value instead of assigning (edit: this deals with std::string
, but see the comments for possible problems with classes that are less user-friendly than string
).
If you back off a little from this part of the code, perhaps you can find a way to not need to set a temporary value at all. Shared mutable state in an object can be bad for the same reason that mutable globals are bad, but to a lesser extent because it only messes up your class instead of messing up your whole program.
For example perhaps you could copy the whole object, set a new value for the variable and visit the copy instead of visiting yourself. Obviously that's not necessarily possible or efficient, you have to look for alternatives on a case by case basis. Maybe the copy can be shallow as far as the children are concerned (i.e. refer to the same child objects), that might be sufficient to make it cheap.
Regarding usage, you could deduce the type like this (untested code):
template <typename T, typename ARG>
TemporaryAssignment<T> temp_value(T &var, ARG &&newvalue) {
return TemporaryAssignment(var, std::forward<ARG>(newValue));
}
Usage:
auto t = temp_value(myVariable, myTemporaryValue);
Then you need a move constructor for TemporaryAssignment:
template <typename TYPE> class TemporaryAssignment {
// change data member
TYPE *mVariable;
TYPE mOriginalValue;
public:
TemporaryAssignment(TYPE &inVariable, TYPE inValue)
: mVariable(&inVariable), mOriginalValue(std::move(inVariable)) {
*mVariable = std::move(inValue);
}
TemporaryAssignment(TemporaryAssignment &&rhs) {
mOriginalValue = std::move(rhs.mOriginalValue);
mVariable = rhs.mVariable;
rhs.mVariable = 0;
}
~TypeAssignment() {
using std::swap;
if (mVariable) {
swap(*mVariable, mOriginalValue);
}
}
// can't remember whether this is needed
TemporaryAssignment(const TemporaryAssignment &) = delete;
TemporaryAssignment &operator=(const TemporaryAssignment &) = delete;
TemporaryAssignment &operator=(TemporaryAssignment &&) = delete;
};
I thought a bit about specifying operator=
for TemporaryAssignment
so as to make the usage look like an assignment, but I didn't come up with anything good.
auto t = (temporary(myVariable) = myTemporaryValue);
is plausible, but you probably don't want TemporaryAssignment
to have operator=
defined because the meaning of:
t = otherTemporaryValue;
isn't necessarily clear and probably shouldn't be allowed. Maybe with a second class to be returned from temporary
, and that returns TemporaryAssignment
from its operator=
.
swap
is not the best solution, because if TYPE
has a std::string
member, the crash problem might happen too.
I think we can use some methods like this:
char buffer[sizeof(TYPE)];
memcpy(buffer, &mVariable, sizeof(TYPE));
memcpy(&mVariable, &mOriginalValue, sizeof(TYPE));
memcpy(&mOriginalValue, buffer, sizeof(TYPE));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With