Sorry about my previous answer folks, what was I thinking? I should have read the question properly.
So, of course, foo()
has to return your ResetGuard
object in order to extend its lifetime, and this is a good thing, not a bad thing.
Firstly, it's hardly a burden on the caller. After all, all he / she has to do is:
auto rg = foo ();
As a potential caller of foo()
I would have absolutely no problem with that, and @melpomene's excellent suggestion in the comments above ([[nodiscard]]
) can be used to ensure that callers don't forget to do this.
And why is forcing the caller to do this a good thing (apart from the fact that you have no choice in the matter anyway)? Well, it gives the caller the opportunity to manage the lifetime of the scopeguard and that might be useful (will provide live demo soon).
As for the other answers here, I would most definitely not hide all this in a macro because that hides an important piece of information from potential callers of foo()
. Instead, I would use [[nodiscard]]
to remind them of their responsibilities and leave it at that.
[Edit]
I have now spent a little time over at Wandbox polishing up the code to add the full set of recommended constructors / assignment operators and to demonstrate the use of [[nodiscard]]
, which for me is the find of the day.
First, the modified class, done in the way (I believe) those who know are recommending. I can particularly see the importance of defining a proper move constructor (just think of the subtle bugs you might run into if you don't). Pinched some stuff (= delete
) from JVApen, looks wise to me, TU JV.
#include <iostream>
#include <assert.h>
#define INCLUDE_COPY_MOVE_SWAP_STUFF
template <class T> class [[nodiscard]] ResetGuard
{
public:
ResetGuard (T& obj_to_reset, const T& new_value) : old_value (obj_to_reset), obj_to_reset (obj_to_reset)
{
obj_to_reset = new_value;
}
#ifdef INCLUDE_COPY_MOVE_SWAP_STUFF
ResetGuard (const ResetGuard& copy_from) = delete;
ResetGuard &operator= (const ResetGuard& copy_assign_from) = delete;
ResetGuard &operator= (ResetGuard&& move_assign_from) = delete;
ResetGuard (ResetGuard&& move_from) : old_value (move_from.old_value), obj_to_reset (move_from.obj_to_reset)
{
assert (!move_from.defunct);
move_from.defunct = true;
}
#endif
~ResetGuard()
{
if (!defunct)
obj_to_reset = old_value;
}
private:
T old_value;
T& obj_to_reset;
bool defunct = false;
};
Comment out #define INCLUDE_COPY_MOVE_SWAP_STUFF
to see the compiler warning you get if you don't do all the things you're supposed to.
Test program:
int GLOBAL_VALUE = 0;
ResetGuard<int> temporarily_set_global_value (int new_val)
{
return { GLOBAL_VALUE, new_val }; // updates GLOBAL_VALUE
}
void bad_foo()
{
temporarily_set_global_value (15);
std::cout << "GLOBAL_VALUE in bad_foo () is " << GLOBAL_VALUE << std::endl;
}
void good_foo()
{
auto rg = temporarily_set_global_value (15);
std::cout << "GLOBAL_VALUE in good_foo () is " << GLOBAL_VALUE << std::endl;
}
auto better_foo()
{
auto rg = temporarily_set_global_value (15);
std::cout << "GLOBAL_VALUE in better_foo () is " << GLOBAL_VALUE << std::endl;
return rg;
}
int main ()
{
bad_foo ();
good_foo ();
std::cout << "GLOBAL_VALUE after good_foo () returns is " << GLOBAL_VALUE << std::endl;
{
auto rg = better_foo ();
std::cout << "GLOBAL_VALUE after better_foo () returns is " << GLOBAL_VALUE << std::endl;
{
auto rg_moved = std::move (rg);
std::cout << "GLOBAL_VALUE after ResetGuard moved is " << GLOBAL_VALUE << std::endl;
}
std::cout << "GLOBAL_VALUE after ResetGuard moved to goes out of scope is " << GLOBAL_VALUE << std::endl;
GLOBAL_VALUE = 42;
}
std::cout << "GLOBAL_VALUE after ResetGuard moved from goes out of scope is " << GLOBAL_VALUE << std::endl;
}
Compiler output:
prog.cc: In function 'void bad_foo()':
prog.cc:47:38: warning: ignoring returned value of type 'ResetGuard<int>', declared with attribute nodiscard [-Wunused-result]
temporarily_set_global_value (15);
^
prog.cc:40:17: note: in call to 'ResetGuard<int> temporarily_set_global_value(int)', declared here
ResetGuard<int> temporarily_set_global_value (int new_val)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:6:40: note: 'ResetGuard<int>' declared here
template <class T> class [[nodiscard]] ResetGuard
^~~~~~~~~~
Program output:
GLOBAL_VALUE in bad_foo () is 0
GLOBAL_VALUE in good_foo () is 15
GLOBAL_VALUE after good_foo () returns is 0
GLOBAL_VALUE in better_foo () is 15
GLOBAL_VALUE after better_foo () returns is 15
GLOBAL_VALUE after ResetGuard moved is 15
GLOBAL_VALUE after ResetGuard moved to goes out of scope is 0
GLOBAL_VALUE after ResetGuard moved from goes out of scope is 42
So there you have it. If you do all the things you're supposed to do (and I hope I have!) then everything works just fine, and it's all nice and efficient thanks to RVO and guaranteed copy elision so there's no need to worry about that either.
Live demo.