Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reverting temporary assignment in C++

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?

like image 705
Neil Steiner Avatar asked Feb 18 '23 12:02

Neil Steiner


2 Answers

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=.

like image 124
Steve Jessop Avatar answered Feb 28 '23 12:02

Steve Jessop


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));
like image 42
idy Avatar answered Feb 28 '23 12:02

idy