Using a global variable is actually not the worst in the world. Spreading unnecessary global variables leads to service nightmares, but in reality it seems like a quick and easy solution. But if you want a clean OO solution, this is certainly possible:
EDIT My initial message does not take into account the fact that you want to perform several operations in sequence, and if any of them is canceled, none of the remaining operations are performed. This means that it is more useful to store the bool flag inside the compensator, rather than separately in each undo operation; and exceptions are the finest way to handle the actual control flow. I also tightened up a few things (added volatile for the flag itself, simplified names, limited unnecessary access rights).
// A thing that can cancel another thing by setting a bool to true. class Canceller { public: Canceller : cancelledFlag(false) {} void RegisterCancellee(Cancellee const& c) { c.RegisterCanceller(cancelledFlag); } void Cancel() { cancelledFlag = true; } private: volatile bool cancelledFlag; }; class CancelButton : public Canceller { ... // Call Cancel() from on-click event handler ... }; class Cancellation : public std::exception { public: virtual const char* what() const throw() { return "User cancelled operation"; } }; // A thing that can be cancelled by something else. class Cancellee { friend class Canceller; // Give them access to RegisterCanceller() protected: Cancellee() : pCancelledFlag(0) {} // Does nothing if unconnected void CheckForCancellation() { if (pCancelledFlag && *pCancelledFlag) throw Cancellation(); } private: void RegisterCanceller(volatile bool& cancelledFlag) { pCancelledFlag = &cancelledFlag; } volatile bool* pCancelledFlag; }; class Op1 : public Cancellee { // (And similarly for Op2 and Op3) ... // Poll CheckForCancellation() inside main working loop ... }; MyThread { CancelButton cancelButton("CANCEL!"); try { ClassWithLongOperation Op1(10); cancelButton.RegisterCancellee(Op1); Op1.Run(); // Takes several minutes. ClassWithLongOperation Op2(20); cancelButton.RegisterCancellee(Op2); Op2.Run(); SomeOtherClassWithLongOperation Op3; cancelButton.RegisterCancellee(Op3); Op3.Run(); } catch (Cancellation& c) { // Maybe write to a log file } // Do some other stuff }
Registering a "double bounce" allows the recipient to allow access to the private flag variable.
Most importantly, do not use thread completion functions, except in very specialized cases. What for? They do not control destructors. They also do not give the target the chance to "clear."
source share