C++11 functional decomposition – easy way to do AOP

This post is about making functional decomposition from perspective of Aspect Oriented Programming using C++11. If you are not familiar with ideas of AOP don’t be afraid – it’s rather simple concept, and by the end of this post you will understand the benefits of it.

You also can treat this post just as example how to use high-order functions in C++11.

In short – AOP tries to perform decomposition of every business function into orthogonal parts called aspects such as security, logging, error handling, etc. The separation of crosscutting concerns. It looks like:

Old picture about AOP

Old picture about AOP

Since C++11 supports high-order functions now we can implement factorization without any additional tools and frameworks (like PostSharp for C#).

You can scroll down to ‘what for’ chapter to check out the result to get more motivated.

PART 1 – TRIVIAL SAMPLE

Let’s start from something simple – one aspect and one function.

Here is simple lambda with trivial computation inside:

I want to add some logging before and after computation. Instead of just adding this boilerplate code into function body let’s go other way. In C++11 we just can write high-order function which will take function as argument and return new function as result:

Here we used std::function, variadic templates and lambda as result. (LOG, NL – my own logging stream and you can just change it with std::cout , std::endl or your another logging lib).

As i hoped to achieve the most simple and compact solution, i expected to use it like this:

Unfortunately this will not compile. ‘no matching function to call ….’ The reason is that lambda is not std::function and automatic type conversion can’t be done. Of cause we can write something like this:

This line will compile, but this is ugly… I hope cpp committee will fix this casting issue. Meanwhile, the best solution i found so far is the following:

This code is using type traits to convert anonymous lambda into std::function of same type. We can use it like this:

Not perfect but much better. Finally we can call functional composition and get the result.

Note: if we had declared aspect function without variadic template we could compose functions without to_function() conversion, but this would kill the benefit from writing universal aspects discussed further.

PART 2 – REALISTIC EXAMPLE

Introduction is over, let’s start some more real-life coding here. Let’s assume we want to find some user inside database by id. And while doing that we also want log the process duration, check that requesting party is authorised to perform such request (security check), check for database request fail, and, finally, check in local cache for instant results.

And one more thing – i don’t want to rewrite such additional aspects for every function type. So let’s write them using variadic templates and get as universal methods as possible.

Ok, let’s start. I will create some dummy implementation for additional classes like User, etc. Such classes are only for example and actual production classes might be completely different, like user id should not be int, etc.

Sample User class as immutable data:

Let’s emulate database as simple vector of users and create one method to work with it (find user by id):

make<> here is just shortcut for make_shared<>, nothing special.

Maybe<> monad

You, probably, noticed that return type of request function contains something called Maybe<T>. This class is inspired by Haskell maybe monad, with one major addition. Instead of just saving Nothing state and Content state, it also might contain Error state.

At first, here is sample type for error description:

Here is minimalistic implementation of Maybe:

Note, that you don’t have to use Maybe<> and here it’s used only for example.

Here we also use the fact that nullptr in C++11 has it’s own type. Maybe has defined constructor from that type producing nothing state. So when you return result from findUser function, there is no need for explicit conversion into Maybe<> – you can just return User or nullptr, and proper constructor will be called.

Operator () returns possible value without any checks, and getError() returns possible error.

Function just() is used for explicit Maybe<T> construction (this is standard name).

Logging aspect

First, let’s rewrite log aspect so it will calculate execution time using std::chrono. Also let’s add new string parameter as name for called function which will be printed to log.

Note std::forward here for passing arguments more clean way. We don’t need to specify return type as Maybe<R> because we don’t need to perform any specific action like error checking here.

‘Try again’ aspect

What if we have failed to get data (for example, in case of disconnect). Let’s create aspect which will in case of error perform same query one more time to be sure.

Maybe<> is used here to identify error state. This method can be extended – we could check error code and decide is there any sense to perform second request (was it network problem or database reported some format error).

Cache aspect

Next thing – let’s add client side cache and check inside it before performing actual server-side request (in functional world this is called memoization). To emulate cache here we can just use std::map:

This function will insert element into cache if it was not there. Here we used that knowledge that cache is std::map, but it can be changed to any key-value container hidden behind some interface.

Second important part, we used only first function argument here as key. If you have complex request where all parameters should act as composite key – what to do? It’s still possible and there are a lot of ways to make it. First way is just to use std::tuple as key (see below). Second way is to create cache class which will allow several key parameters. Third way is to combine arguments into single string cache using variadic templates. Using tuple approach we can rewrite it like this:

Now it’s much more universal.

Security aspect

Never forget about security. Let’s emulate user session with some dummy class –

Security checking high-order function will have additional parameter – session. Checking will only verify that isValid() field is true:

‘Not empty’ aspect

Last thing in this example – let’s treat not found user as error.

Im not writing here about error handling aspect, but it’s also can be implemented via same approach. Note that using error propagation inside Maybe<> monad you can avoid using exceptions and define your error processing logic different way.

Multithread lock aspect

No comments.

FINALLY

Finally, what for was all this madness?  FOR THIS LINE:

Checking (let’s find user with id 2):

Ok, let’s perform tests for several users ( here we will request same user twice and one non-existing user ):

As you can see it’s working as intended. It’s obvious that we got a lot of benefits from such decomposition. Factorisation leads to decoupling of functionality, more modular structure and so on. You gain more focus on actual business logic as result.

We can change order of aspects as we like. And as we made aspect functions rather universal we can reuse them avoiding a lot of code duplication.

Instead of functions we can use more sophisticated functors (with inheritance), and instead of Maybe<> also could be more complex structure to hold some additional info. So whole scheme is extendable.

Note also, that you can pass lambdas as additional aspect parameters.

Working sample to play with: github gist or  ideone

Ps. BONUS:

 

  • King Yan Kwan

    Ha! You hit on the same solution I did with regards to the nullptr constructor: http://worldengineer.me/2014/08/18/42/

    Technically you don’t need to use decltype(nullptr), because the standard already defines the type as std::nullptr_t

    • Victor Laskin

      Nice to hear it :) Sure, you can use nullptr_t instead of decltype(nullptr).

  • Scott Prager

    Hi, in to_function, “static_cast<typename function_traits::function>(lambda);” should really just be “typename function_traits::function(lambda);”. A static cast adds no real safety here.

    • Victor Laskin

      Thanks, you are right, its working without cast. I will change in text accordingly.

  • http://insooth.i365.pl/insooth/ fj

    That looks like ordinary programming with Monads (and Applicative in for_each_argument) in Haskell. But here we have C++ with tons of -ish code instead :-)

    • Victor Laskin

      Actually, its relatively low amount of -ish code for such task in cpp, but of cause, in haskell such composition is much more terse.

      • http://insooth.i365.pl/insooth/ fj

        That’s true. Just trying to do similar stuff in C++03 with all those hand written function objects and lack of decltype might be a nightmare of hundreds of lines of highly templated code! I am very glad we have C++11-enabled compilers available today, and, hopefully, one day I will be allowed to use C++11 in the production code…

  • Martin Moene

    Nice article!

    On a different aspect 😉
    – property of it: its property
    – it is: it’s
    – let us: let’s

    • Victor Laskin

      Hmm, thanks. I fixed missing apostrophes in text. Sorry :)

  • Pingback: Aspekt- und Objektorientiertes C++ - Felix' Blog - Felix' Blog()