Provide a safe interface to a class that ensures its internal guaranties without closing too much the class interface.
Motivation
Designing a C++ concurrent library can be done in a simple way by defining a wrapper of the types, constants, and functions of an existing C thread library. For example the class mutex is used to ensure mutually exclusive access to data.
class mutex { public: // … void lock(); void unlock(); // … };
While the C++ wrapper interface can eliminate some of the C liabilities, it doesn't avoid the fact that client must master the protocol usage: the client must guarantee that the unlock function is called each time the lock function is called.
{ mtx.lock(); // … [a] // not called if an exception occurs in [a] mtx.unlock(); }
A first step forward could consist in using the C++ scoped idiom (RAII) to define some scoped classes that will acquire the mutex on construction and release it on destruction.
class mutex_strict_lock { public : mutex_strict_lock(mutex& mtx) : mtx_(mtx) { mtx_.lock(); } ~mutex_strict_lock(mutex&) { mtx_.unlock(); } private : mutex mtx_; };and use it in a scoped block:
{ mutex_strict_lock guard(mutex); // … // destructor called when exit from this block // even on exception }
While this reduces risk, it does not force the client to only use this safe mechanism in order to lock/unlock a mutex, since the lock and unlock functions remain public.
A second step could be to declare the lock/unlock functions private and to grant friend access to the mutex_strict_lock class.
class mutex { public: // ... private: // … void lock(); void unlock(); // … friend class mutex_strict_lock; // ... };Now the following code does not compiles
{ mtx.lock(); // ERROR do not compile // .. [a] // not called if an exception occurs in [a] mtx.unlock();// ERROR do not compile }
This ensures a safe use of the class but there is no other way to use a mutex, the interface to the mutex class is now close. Once the mutex is locked, it cannot be unlocked neither relocked as many times as the client considers pertinent. A close interface is less useful than an unsafe one.
The main problem with the mutex_strict_lock class is that even if it ensures the mutex guaranties, it adds another one; the mutex stays locked during the lifetime of the mutex_strict_lock instance. Moreover, the safe adapter must ensure the guaranties but not limit the initial set of functionalities. Sometimes this can be done with a single safe adapter class or with a set of safe adapters classes. So the designer of the class must define at least a minimal set of safe adapters that covers the whole set of functionalities. In the case of the mutex class, a mutex_scoped_lock can ensure the guaranties and satisfy all the functionalities.
Once we have a minimal set of safe adapters, the designer can find some other interesting usages of its class, which could in addition ensure other guaranties, and then grant friend access to all this classes.
class mutex { // ... friend class mutex_scoped_lock; friend class mutex_strict_lock; friend class mutex_scoped_reverse_lock; //... };
This could seems good, but the library designer can not pretend to know in advance all the safe usages of its library while each of the safe adapters needs to access the lock/unlock functions. There are other ways to ensure the guaranties, as a mutex_locking_ptr, mutex_scoped_locking_ptr, and surely many others. But now we cannot use these new safe classes without modifying the initial class.
What we are looking for is not to limit the safe usage of the initial class, but just to protect from unsafe usages. The idea is to define a two levels interface for a class, one being safe while the other is unsafe but only visible to safe aware clients. Evidently there is no means to check this safety awareness, but the fact the interface is split in two interfaces having different usages, helps the unaware client to identify the safe interface, and signals to the client that using the unsafe interface s/he must be aware of the unsafe features.
What we are looking for is not to limit the safe usage of the initial class, but just to protect from unsafe usages. The idea is to define a two levels interface for a class, one being safe while the other is unsafe but only visible to safe aware clients. Evidently there is no means to check this safety awareness, but the fact the interface is split in two interfaces having different usages, helps the unaware client to identify the safe interface, and signals to the client that using the unsafe interface s/he must be aware of the unsafe features.
More coming soon, stay tunned.
No comments:
Post a Comment