Re: Avoiding the anonymous variable declaration RAII error...
On Mar 11, 11:42 am, "Eric J. Holtman" <e...@ericholtman.com> wrote:
I've burned myself twice in seven or so years doing this, and
it seems like there ought to be a clever way to avoid it:
I have a Mutex class, and a Locker class which acquires
a mutex in its constructor, and releases it in its destructor,
so you can write the standard code:
void
process_threaded_data ()
{
Locker lk (m_mutex);
}
However, it's a massive race condition just waiting to happen
if you forget the "lk", and just write:
void
process_threaded_data ()
{
Locker (m_mutex);
.
Is there some way to design class Locker so that writing the
second piece of code comes up as an error?
Yes, it is possible to force a C++ program to name each "Locker"
variable that it declares. One, easy solution would be to make Locker
objects "default-constructible." After all, if a Locker object is
declared without any initializers - then the object must have a name.
Otherwise, having the Locker class name all by itself, like so:
Locker; // Error
causes a compiler error (at least, gcc rejects the nameless
declaration).
But, eliminating Locker's Mutex initializer might not be a practical
solution. In that case, a much more elaborate solution will be needed.
The strategy employed by this more complicated approach is based on a
single observation: namely, that the parentheses that surround the
Locker object's initializer are the crux of the problem. In
particular, if the program were unable to initialize a Locker object
with a parenthesized value - and had instead to resort to initializer
assignment; then the Locker object would have to be named for the
simple reason that assignement to an unnamed object is not possible in
C++.
For example, an unnamed Locker variable may be parenthesized-value
initialized in C++:
Locker(myMutex); // OK
but the same unnamed variable may not be assigned-value initialized in
C++:
Locker = myMutex: // Error
So, the challenge becomes: how does the Locker implementation force
its clients to use assignment syntax (and not parenthesized syntax) to
initialize Locker objects.
At first, C++'s "explicit" constructors might appear to offer a likely
solution. After all, a C++ class can use explicit constructors to
force the class's clients to use parenthesized value expressions to
initialize its objects. Unfortunately, there is no way for a class to
use explicit constructors to achieve the opposite effect - that is, to
require clients to use assignment initializers.
As it turns out, the solution comes from a close reading of ?8.5/14 -
and noting in particular - that any user-defined conversions that
might be needed to convert an initializer object to the class being
initialized - are considered only in case that the object is being
initialized with the assignment syntax. Therefore, by implementing a
Mutex-to-Locker user conversion, it is possible to force Locker
clients to use assignment initialization when declaring a Locker
variable - and thereby also require the client to name the variable.
For example:
struct Locker;
struct Mutex
{
// ...
operator Locker(); // user-defined conversion
};
struct Locker
{
private:
friend class Mutex;
Locker(const Mutex& m) {}
public:
// ...
};
int main()
{
Mutex m;
Locker(m); // Error: constructor is private
Locker = m; // Error: expected unqualified-id
Locker locker = m; // OK
...
}
Now, given the fact that unnamed Locker objects have caused only two
bugs in seven years, it is doubtful that implementing the elaborate
(and somewhat convoluted) solution above - really makes sense. Unless,
of course, these two bugs were the only two bugs that existed in the
software during those seven years. In that case, it would make sense
to implement the solution presented above because - as every C++
programmer knows - eliminating the last two bugs in any C++ program of
moderate to extreme complexity - is always a nice achievement.
Greg
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]