Hi, I am Gabor from the Visual C++ static analysis team. This team is working on tools to find
semantic errors in programs at compile time. Good APIs are easy to use and hard to misuse. In fact
the better the APIs are the easier my team's job will be. Today I will talk about a small trick how
to make some APIs safer. Some APIs are inherently ordered. Let's look at this std::mutex example.
Here I'm calling unlock first and lock after. Calling these methods in this order
results in undefined behavior. Fortunately we have some static checks to find these errors.
But how wonderful would it be if the incorrect version would not even compile in the first place.
In this specific case we can use RAII (Resource Acquisition Is Initialization). We can create an object that calls the lock
function in its constructor and calls the unlock function in its destructor. This will
not only ensure that we call lock first and unlock later, but it also ensures that we never forget
to call unlock. And this works great when we are calling lock and unlock in the same lexical scope,
but what can we do if we want to call lock in one function
and unlock in another. We can use move semantics to do that. Using
move semantics we can transfer the ownership of a resource from one RAII object to another.
So we can have some pads when we call unlock in function g instead of function f. In this example
however we were lucky we only had two functions to think about: lock and unlock. This is why we could
map those to constructor and destructor calls. What if we have a type that is way more complicated.
Here we have a type with five functions and we have some ordering relationships between them.
We can capture these ordering relationships using a finite state machine .
Here after an object is constructed I can call one of the three methods a, b, or c, and after
picking a method, let's say a, I can only call the method d and I cannot call method e. In this talk
I will show you how to design an API that can statically enforce these ordering contracts. Let's
create the helper class first. This helper class is default constructable and move constructable.
It also has a small state. It is okay to destruct an instance of this class that was moved from,
but it will trigger an assertion failure if we destruct an instance that wasn't moved from. It
might not be clear yet why do we need this but bear with me; with some examples it will be crystal
clear. Let's look at the implementation of my type. a, b, and c return special key types. These keys
cannot be constructed by the caller, the only way to obtain them is to use these methods. e
and d are accepting slots. These slots can be created using conversion rules and keys.
By introducing conversion rules between the keys and the slots we can represent the ordering edges
of the finite state machine. In the conversion from a key to a slot we also make sure
to correctly track the consumed state of the key. Let's look at an example how can we use my type.
Here I can use auto to obtain a key from method a and then I have to move the key
into method d to be able to call it. If I'm trying to call method e instead
I will get a compilation error. If I forget to call method e or method d I will get a runtime
error. Note that the code I showed you was simplified so it can be fit on the slides,
so there are some additional properties that could be validated. I left those as an exercise.
To summarize the talk we talked about RAII and ownership transfers and I also showed you
how can we validate certain aspects of API orderings statically and other aspects
dynamically. I hope this was useful. Thank you for listening and I hope to see you at Pure Virtual!