Monthly Archives: September 2012

A Place for Override, and Every Override in its Place

“I love C++!”, said an enthusiastic job candidate during an interview a year ago.  That candidate has since become one of the best developers I ever hired.  I too love C++, and especially so during its current renaissance.  Prior to C++11, the last time I felt this way was at the 1997 Visual C++ Developers Conference in Orlando.  Here, C++98 improvements like explicit and mutable were unveiled, causing many a developer to run back to their ARM to review “const correctness”, which had more or less been ignored to that point.  If C++98 was evolutionary, C++11 is revolutionary – a tour de force of new features.  Some, like lambdas, are bold forays into the archrival territory of functional programming.  The most profound new feature, from my perspective, is Rvalue References.  These enable move semantics (construction and assignment), which are potentially far more efficient than their copy counterparts.  For a language that lacks access to the compiler’s parse tree, features like Rvalue References permit surprisingly subtle expressions for optimal code generation.

So the C++ language is alive and well, and its priesthood is openly solicitous of new ideas from the community.  I’ll oblige with a series of posts on features I’d like to see added to the language.  Each of these has been born of necessity, when in the throes of a coding session I lamented that I had to once again resort to workarounds.

To kick things off, let’s consider override.   This new keyword (strictly, an “identifier”, but I’ll use “keyword” for simplicity and clarity) allows the programmer to make a distinction between an override of an existing virtual method, and the introduction of a new virtual method.  Previously, the keyword virtual was overloaded for this purpose and often led to errors when virtual dispatching would fail due to a refactoring of base or derived classes.  Let’s see how this could happen.

Initially the code looks like this:

struct Base
{
	virtual void foo()
	{
		cout << "Base::foo called" << endl;
	}
};

struct RiskyDerived : public Base
{
	// "virtual" is optional, making overrides difficult to track down
	void foo()
	{
		cout << "RiskyDerived::foo called" << endl;
	}
};

RiskyDerived derived;
Base & base = derived;
base.foo();	// "RiskyDerived::foo called"

After a hasty refactor, we now have this:

struct Base
{
	virtual void foo(int = 0)
	{
		cout << "Base::foo called" << endl;
	}
};

RiskyDerived derived;
Base & base = derived;
base.foo(); // "Base::foo called"

The binding of BrokenDerived::foo to the vtable entry of Base::foo is now broken, but the code still compiles, resulting in a runtime error (the worst kind).  But with the override keyword in place, we are protected from situations like this because the compiler will issue an error:

struct BetterDerived : public Base
{
	// Error: member function declared with 'override' does not override a base class member
	void foo() override;
};

The problem with override is that it’s only a half solution.  My development team has been bit by the situation above on a number of occasions.  So naturally, when a new keyword like override is introduced, we eagerly embrace it and look for a way to systematically employ it throughout the codebase.  That’s where override breaks down.  In its current implementation, systematic use is not feasible, because the keyword is optional.

What we have today is this behavior:

All uses of the override keyword must in fact override a virtual method. 

What we’re lacking is a way to enforce the contrapositive:

If a virtual method lacks the override keyword, it must not override a virtual method. 

We might term the current behavior “weak” or “permissive”, and my ideal behavior “strong” or “strict”.  In “strict” mode, without the override keyword, a method declaration must either introduce:

  1. A new virtual method (if accompanied by the virtual keyword) or
  2. A new non-virtual method, possibly hiding or overloading another method of the same name.

In short, all virtual method overrides must use the override keyword in “strict” mode.

I understand that a “strict override” would be a breaking change to much existing code.  But that is the whole point:  I would like the compiler to tell me where I need to use override (or not).  Because the virtual keyword is optional when declaring an overridden virtual method, it is a tedious and error prone exercise to manually track down all appropriate locations for override in an existing codebase.   It’s true that some compilers will warn of method hiding, if the signature alone changes.  But if the name of the method changes, the compiler is silent as the grave.

In “strict” mode, on the other hand, our hypothetical compiler could issue a warning such as this:

struct BestDerived : public Base
{
	// Warning: member function overrides a base class member, but is not declared with 'override'
	void foo();
};

Perhaps this is best left as a compiler extension, due to the concern over breaking changes.  But it surprises me that no compiler, to my knowledge, currently offers it.  At a minimum, as a user of Microsoft’s compiler, it seems to me that a third-party tool could be created that scans the Intellisense database to determine “strict override” violations.  Any takers?  Visual Assist team?

Concurrency is an Atomic Operation

Our architect came into my office recently with a conundrum.  We’d recently jumped into concurrency as part of a performance-tuning overhaul.  After some profiling, we identified a few hotspots in program initialization and in paths that need to maintain user-responsiveness.  The solution was deceptively simple:  adopt a parallel for-each in place of the existing sequential for-each.  After debugging the thread-unsafe parts of the legacy code invoked from the parallel for-each, we achieved success.  Or so we thought.  It turns out that ensuring thread-safety along all code paths executed from the parallel for-each, through the course of routine maintenance, is a challenging task.  Another programmer on the team later made an innocent change to the code invoked by the parallel for-each, introducing a race condition.  It wasn’t clear whose responsibility this was – the breaking change could have been arbitrarily distant from the parallel for-each.

 

In functional languages, immutability and pure functions (non side-effecting) are core concepts.  Many argue that these lead to code that is easier to reason about, and that concurrency is nearly a natural consequence.  As Herb Sutter says, “shared state leads to contention”.  The corollary is that avoiding shared state can eliminate concurrency concerns (the idea behind lock-free algorithms).  Sharing of state occurs not just in obvious places, like global variables.  It also occurs in local mutable variables (or members), which are within scope for more than one thread of execution.

 

The problem is that idiomatic C++ is littered with such mutable state and side-effecting functions.  I say “idiomatic” and not “legacy” C++.  In this new world order of threading support in the STL, it is oh-so-easy to manage threads – their creation, synchronization, invocation, etc.  What’s lacking is a best practice for ensuring that the code executed by threads is safe.  Some C++ shops have adopted a pure functional style of programming to ensure thread safety.  This is noble and impressive, but in my view impractical.  Such code may be thread-safe itself, but must still interface with other C++ code (libraries) which is still idiomatic and thus, not thread-safe.  How is thread-safety to be achieved then?  The fact that even the venerable go-to collection class, vector, must essentially be rewritten is instructive.

 

The architect concluded that we had two options:  roll back all concurrency work, or train all the developers on staff to be aware of threading and to write ALL of their code to be thread-safe.  The former was not an option – we had crossed the Rubicon.  In the end, we identified two techniques to achieve the latter:  (1) a “top down” (from the parallel for-each) analysis of const correctness; and (2) a “bottom up” analysis of “mutable correctness” (synchronizing access to shared state).  But the lesson for us was that with concurrency, you cannot “buy it by the yard” or delegate the responsibility to the local subject matter expert.  In other words, the move to concurrency in an organization must be atomic.  In that sense, as Sutter has also said, the free lunch is still over.