-
Notifications
You must be signed in to change notification settings - Fork 1
Compile Time Constraints
With the vwpp
library, you can enforce constraints so that functions cannot be misused. For instance, you'd like to break some code up into functions, but these functions need to be run with a locked mutex. You can't lock and unlock the mutex in each function because the entire process is no longer atomic (i.e. other tasks might get the mutex between function calls.) The obvious solution is to wrap the section that calls the functions:
vwpp::Mutex mtx;
// Must own 'mtx' when calling!
static void action_a()
{ /* ... */ }
// Must own 'mtx' when calling!
static void action_b()
{ /* ... */ }
int f()
{
vwpp::Mutex::Lock<mtx> lock;
action_a();
action_b();
}
This works and it allows you to build simpler actions that can be mixed and matched in different ways. The problem is you're enforcing the mutex ownership through comments. There is nothing preventing a future maintainer from accidentally (or intentionally!) calling your action functions without locking the mutex.
This is simple to fix:
vwpp::Mutex mtx;
static void action_a(vwpp::Mutex::Lock<mtx>&)
{ /* ... */ }
static void action_b(vwpp::Mutex::Lock<mtx>&)
{ /* ... */ }
int f()
{
vwpp::Mutex::Lock<mtx> lock;
action_a(lock);
action_b(lock);
}
We add Mutex::Lock<mtx>
as an (unused) parameter to functions that need to run in the context of the locked mutex. Now the functions can be mixed and matched, but the compiler is enforcing mutex usage -- not the comments. Note also, that the lock specifies in a template parameter which mutex needs to be locked. This guarantees that the correct mutex is held during the execution of both functions.
The library defines other lock objects which can be used in a similar way. If a function needs to be run with interrupts disabled, the IntLock
class can be used:
static void action(vwpp::IntLock&)
{ /* ... */ }
void f()
{
// Interrupts active here.
{
vwpp::IntLock lock;
// Interrupts disabled.
action(lock);
}
// Interrupts active here.
}
The vwpp
library has two objects that can temporarily adjust the priority of a task. The adjustment is specified as a template argument rather than a constructor argument. This was done for a subtle, but important reason; in C++, each template instantiation is a unique type. For example, if vwpp::AbsPriority
was a simple class, the constructor would have to take the priority setting and all instances of vwpp::AbsPriority
would be the same type. Templates are different. vwpp::AbsPriority<50>
is a completely different and incompatible type from vwpp::AbsPriority<100>
. This means we can do something like this:
static void f(vwpp::AbsPriority<30>&)
{
}
The function f
wants to be run at priority 30 (maybe it wants to make sure the network task doesn't interrupt it.) This will enforce, at compile time, that the caller has switched to priority level 30. Using a normal class would require the check be done at run-time and an exception get thrown or error returned, which requires error checking.
Of course the function could switch to priority 30 itself. But if one was programming in this style (where small functions are created to build a set of primitives with constraints), then the resulting pieces can only be put together in certain ways and the compiler helps enforce their use!