0% found this document useful (0 votes)
113 views21 pages

Declarative Control Flow - Andrei Alexandrescu - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
113 views21 pages

Declarative Control Flow - Andrei Alexandrescu - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 21

Declarative Control Flow

Prepared for CppCon


Bellevue, WA, Sep 20–25, 2015

Andrei Alexandrescu, Ph.D.


[email protected]

© 2015- Andrei Alexandrescu. Do not redistribute. 1 / 41

Introduction

There is no introduction slide

© 2015- Andrei Alexandrescu. Do not redistribute. 2 / 41


Challenge

• Define a transactional file copy function


• Either succeed
• . . . or fail successfully

© 2015- Andrei Alexandrescu. Do not redistribute. 3 / 41

Cheat Sheet

• From boost::filesystem:
void copy_file(
const path& from,
const path& to
);

© 2015- Andrei Alexandrescu. Do not redistribute. 4 / 41


Implementation

void copy_file_transact(const path& from,


const path& to) {
bf::path t = to.native() + ".deleteme";
try {
bf::copy_file(from, t);
bf::rename(t, to);
} catch (...) {
::remove(t.c_str());
throw;
}
}

© 2015- Andrei Alexandrescu. Do not redistribute. 5 / 41

Meh

• We now have explicit control flow


◦ Not dedicated to core logic
• Of 8 lines, only 4 do “work”
• Y u play baseball?

© 2015- Andrei Alexandrescu. Do not redistribute. 6 / 41


Composition

• Define a transactional file move function


• Either succeed
• . . . or fail successfully

© 2015- Andrei Alexandrescu. Do not redistribute. 7 / 41

Implementation

void move_file_transact(const path& from,


const path& to) {
try {
bf::copy_file_transact(from, to);
bf::remove(from);
} catch (...) {
::remove(to.c_str());
throw;
}
}

© 2015- Andrei Alexandrescu. Do not redistribute. 8 / 41


Ehm

• Same issues
• Also, functions that throw don’t compose all
that well

• Oops, there’s a bug

© 2015- Andrei Alexandrescu. Do not redistribute. 9 / 41

Implementation (fixed)

void move_file_transact(const path& from,


const path& to) {
bf::copy_file_transact(from, to);
try {
bf::remove(from);
} catch (...) {
::remove(to.c_str());
throw;
}
}

© 2015- Andrei Alexandrescu. Do not redistribute. 10 / 41


What Tools Do We Have?

• RAII tenuous
• ScopeGuard tenuous
• Composition only makes it worse
◦ Series/nesting of try/catch
◦ Urgh

• Meantime in CppCoreStandards:
“E.18: Minimize the use of explicit try/catch”

© 2015- Andrei Alexandrescu. Do not redistribute. 11 / 41

Suddenly. . .

goto fail; doesn’t look that bad


anymore, eh?

© 2015- Andrei Alexandrescu. Do not redistribute. 12 / 41


Explicit Control Flow =
Fail

© 2015- Andrei Alexandrescu. Do not redistribute. 13 / 41

Declarative Programming

• Focus on stating needed accomplishments


• As opposed to describing steps
• Control flow typically minimal/absent
• Execution is implicit, not explicit
• Examples: SQL, regex, make, config,. . .

• Let’s take a page from their book!

© 2015- Andrei Alexandrescu. Do not redistribute. 14 / 41


Surprising Insight

• Consider bona fide RAII with destructors:

X States needed accomplishment?


X Implicit execution?
X Control flow minimal?

• RAII is declarative programming!

© 2015- Andrei Alexandrescu. Do not redistribute. 15 / 41

More RAII: ScopeGuard

• Also declarative
• Less syntactic baggage than cdtors
• Flow is “automated” through placement
• Macro SCOPE_EXIT raises it to
pseudo-statement status

© 2015- Andrei Alexandrescu. Do not redistribute. 16 / 41


Pseudo-Statement (old hat!)

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 ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::detail::ScopeGuardOnExit() + [&]()

© 2015- Andrei Alexandrescu. Do not redistribute. 17 / 41

Preprocessor Trick (old hat!)

#define CONCATENATE_IMPL(s1, s2) s1##s2


#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)

#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__)
#endif

© 2015- Andrei Alexandrescu. Do not redistribute. 18 / 41


Use (old hat!)

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); };

... use fd and buf ...


}
(if no “;” after lambda, error message is fail)

© 2015- Andrei Alexandrescu. Do not redistribute. 19 / 41

Painfully Close to Ideal!

haction1i
SCOPE_EXIT { hcleanup1i };
SCOPE_FAIL { hrollback1i }; // nope
haction2i
SCOPE_EXIT { hcleanup2i };
SCOPE_FAIL { hrollback2i }; // nope

© 2015- Andrei Alexandrescu. Do not redistribute. 20 / 41


One more for completeness

haction i
SCOPE_SUCCESS { hcelebrate i };
hnext i

• Powerful flow-declarative trifecta!


• Do not specify flow
• Instead declare circumstances and goals

© 2015- Andrei Alexandrescu. Do not redistribute. 21 / 41

May Will Has become


100% portable:
C++17 has it!
http://isocpp.org/files/papers/N4152.pdf

© 2015- Andrei Alexandrescu. Do not redistribute. 22 / 41


N4152

• “When in doubt, replace bool with int”

namespace std {
bool uncaught_exception(); // old and bad
int uncaught_exceptions(); // new and rad
}

© 2015- Andrei Alexandrescu. Do not redistribute. 23 / 41

Using std::uncaught_exceptions

class UncaughtExceptionCounter {
int getUncaughtExceptionCount() noexcept;
int exceptionCount_;
public:
UncaughtExceptionCounter()
: exceptionCount_(std::uncaught_exceptions()) {
}
bool newUncaughtException() noexcept {
return std::uncaught_exceptions()
> exceptionCount_;
}
};

© 2015- Andrei Alexandrescu. Do not redistribute. 24 / 41


Layering

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_();
}
}
};

© 2015- Andrei Alexandrescu. Do not redistribute. 25 / 41

Icing

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));
}

© 2015- Andrei Alexandrescu. Do not redistribute. 26 / 41


Cake Candles

#define SCOPE_FAIL \
auto ANONYMOUS_VARIABLE(SCOPE_FAIL_STATE) \
= ::detail::ScopeGuardOnFail() + [&]() noexcept

© 2015- Andrei Alexandrescu. Do not redistribute. 27 / 41

Back to Example

© 2015- Andrei Alexandrescu. Do not redistribute. 28 / 41


Transactional Copy

void copy_file_transact(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);
}
• All code does work

© 2015- Andrei Alexandrescu. Do not redistribute. 29 / 41

Transactional Move

void move_file_transact(const path& from,


const path& to) {
bf::copy_file_transact(from, to);
SCOPE_FAIL { ::remove(to.c_str()); };
bf::remove(from);
}
• Simpler than the no-exceptions version!

© 2015- Andrei Alexandrescu. Do not redistribute. 30 / 41


Please Note

Only SCOPE_SUCCESS
may throw

© 2015- Andrei Alexandrescu. Do not redistribute. 31 / 41

Postconditions

int string2int(const string& s) {


int r;
SCOPE_SUCCESS {
assert(int2string(r) == s);
};
...
return r;
}

© 2015- Andrei Alexandrescu. Do not redistribute. 32 / 41


Changing of the Guard

void process(char *const buf, size_t len) {


if (!len) return;
const auto save = buf[len - 1];
buf[len - 1] = 255;
SCOPE_EXIT { buf[len - 1] = save; };
for (auto p = buf;;) switch (auto c = *p++) {
...
}
}

© 2015- Andrei Alexandrescu. Do not redistribute. 33 / 41

Scoped Changes

bool g_sweeping;
void sweep() {
g_sweeping = true;
SCOPE_EXIT { g_sweeping = false; };
auto r = getRoot();
assert(r);
r->sweepAll();
}

© 2015- Andrei Alexandrescu. Do not redistribute. 34 / 41


No RAII Type? No Problem!

void fileTransact(int fd) {


enforce(flock(fd, LOCK_EX) == 0);
SCOPE_EXIT {
enforce(flock(fd, LOCK_UN) == 0);
};
...
}
• No need to add a type for occasional RAII
idioms

© 2015- Andrei Alexandrescu. Do not redistribute. 35 / 41

Remarks

• All examples taken from production code


• Declarative focus
◦ Declare contingency actions by context
• SCOPE_* more frequent than try in new code
• The latter remains in use for actual handling
• Flattened flow
• Order still matters

© 2015- Andrei Alexandrescu. Do not redistribute. 36 / 41


Only detail left:
std::uncaught_exceptions()

© 2015- Andrei Alexandrescu. Do not redistribute. 37 / 41

Implemented and
maintained on ALL
major compilers

© 2015- Andrei Alexandrescu. Do not redistribute. 38 / 41


Credits

• Evgeny Panasyuk: compiler-specific bits


github.com/panaseleus/stack_unwinding

• Daniel Marinescu: folly implementation


github.com/facebook/folly

• Herb: got it in C++17


• Michael: Implemented it in Visual Studio
2015

• Ville Voutilainen implemented it for gcc

© 2015- Andrei Alexandrescu. Do not redistribute. 39 / 41

Summary

© 2015- Andrei Alexandrescu. Do not redistribute. 40 / 41


Summary

There is no summary slide

© 2015- Andrei Alexandrescu. Do not redistribute. 41 / 41

You might also like