void copy_file_tr(const path& from, const path& to) { path tmp=to+".deleteme"; try { copy_file(from, tmp); rename(tmp, to); } catch(...) { ::remove(tmp.c_str()); throw; } }
Whatever happens during copying, the temporary file will be deleted, which is what we needed. However, if you look at just three lines of meaningful code, all the rest is a check of the success of a function call via try / catch, that is, manual control of execution. The program structure here does not reflect the real logic of the task. Another unpleasant moment is that this code strongly depends on the obviously undescribed properties of the called functions, so the rename () function is assumed to be atomic (transactionally stable), and remove () should not throw exceptions (why here it is used :: remove () instead of boost: : filesystem :: remove ()). Let's make it even worse and write the move_file_tr pair function: void move_file_tr(const path& from, const path& to) { copy_file_tr(from, to); try { remove(from); } catch(...) { ::remove(to.c_str()); throw; } }
We see all the same problems here, in such a tiny piece of code we had to add another try / catch block. Moreover, even here you can already see how badly this code is scaled, each block enters its own scope, the intersection of blocks is impossible, etc. If you are not convinced yet, the standard recommends minimizing the manual use of try / catch, for “verbose and non-trivial uses error-prone.” Let's say directly and honestly that direct control of performance details does not suit us anymore, we want more . namespace detail { enum class ScopeGuardOnExit {}; template<typename<Fun> ScopeGuard<Fun> operator+ (ScopeGuardOnExit, Fun&& fn) { return ScopeGuard<Fun>(std::forward<Fun>(fn)); } } #define SCOPE_EXIT \ auto ANONIMOUS_VARIABLE(SCOPE_EXIT_STATE) \ = ::detail::ScopeGuardOnExit + (&)[] }
In fact, this is half the definition of a lambda function, the body must be added when calling. #define CONACTENATE_IMPL(s1,s2) s1##s2 #define CONCATENATE(s1,s2) CONCATENATE_IMPL(s1,s2) #define ANONYMOUS_VARIABLE(str) CONCATENATE(str,__COUNTER__)
With the use of such a construction, the usual C ++ code at once acquires the features unseen at once: void fun() { char name[] = "/tmp/deleteme.XXXXXX"; auto fd = mkstemp(name); SCOPE_EXIT { fclose(fd); unlink(name); }; auto buf = malloc(1024*1024); SCOPE_EXIT { free(buf); }; ... }
So, it is argued that for a full transition to the declarative style, it is enough for us to define two more similar macros - SCOPE_FAIL and SCOPE_SUCCESS; using this triple, you can separate the logically significant code and detailed control instructions. For this we need and it is enough to know whether the destructor is called, normally or as a result of unwinding the stack. And such a function is in C ++ - bool uncaught_exception () , it returns true if it was called from inside a catch block. However, there is one unpleasant nuance - this function is broken in the current version of C ++ and does not always return the correct value. The fact is that it does not distinguish whether the call to the destructor is part of unwinding the stack or whether it is a regular object on the stack created inside the catch block, you can read more about it from the original source . Anyway, in C ++ - 17 this function will be officially declared deprecated and another entered instead - int uncaught_exceptions () (find the two differences yourself), which returns the number of nested handlers from which it was called. We can now create a helper class that shows exactly, call SCOPE_SUCCESS or SCOPE_FAIL: class UncaughtExceptionCounter { int getUncaughtExceptionCount() noexcept; int exceptionCount_; public: UncaughtExceptionCounter() : exceptionCount_(std::uncaught_exceptions()) {} bool newUncaughtException() noexcept { return std::uncaught_exceptions() > exceptionCount_; } };
It's funny that this class itself also uses RAII to capture the state in the constructor. template <typename FunctionType, bool executeOnException> class ScopeGuardForNewException { FunctionType function_; UncaughtExceptionCounter ec_; public: explicit ScopeGuardForNewException(const FunctionType& fn) : function_(fn) {} explicit ScopeGuardForNewException(FunctionType&& fn) : function_(std::move(fn)) {} ~ScopeGuardForNewException() noexcept(executeOnException) { if (executeOnException == ec_.isNewUncaughtException()) { function_(); } } };
Actually, all the interesting is concentrated in the destructor, it is there that the state of the exception counter is compared with the template parameter and the decision is made to call or not the internal functor. Pay attention as the same template parameter delicately defines the destructor signature: noexcept (executeOnException) , since SCOPE_FAIL should be exception safe, and SCOPE_SUCCESS can completely exclude itself, finally, from harm. In my opinion, it is precisely such minor architectural details that make C ++ the very language I like. enum class ScopeGuardOnFail {}; template <typename FunctionType> ScopeGuardForNewException< typename std::decay<FunctionType>::type, true> operator+(detail::ScopeGuardOnFail, FunctionType&& fn) { return ScopeGuardForNewException< typename std::decay<FunctionType>::type, true >(std::forward<FunctionType>(fn)); } #define SCOPE_FAIL \ auto ANONYMOUS_VARIABLE(SCOPE_FAIL_STATE) \ = ::detail::ScopeGuardOnFail() + [&]() noexcept
And similarly for SCOPE_EXIT void copy_file_tr(const path& from, const path& to) { bf::path t = to.native() + ".deleteme"; SCOPE_FAIL { ::remove(t.c_str()); }; bf::copy_file(from, t); bf::rename(t, to); } void move_file_tr(const path& from, const path& to) { bf::copy_file_transact(from, to); SCOPE_FAIL { ::remove(to.c_str()); }; bf::remove(from); }
The code looks more transparent, moreover, each line means something. Here is an example of using SCOPE_SUCCESS, along with a demonstration of why this macro can throw exceptions: int string2int(const string& s) { int r; SCOPE_SUCCESS { assert(int2string(r) == s); }; ... return r; }
Thus, a very small syntactic barrier prevents us from adding another declarative style to C ++.Source: https://habr.com/ru/post/270545/
All Articles