What’s the point of a const member function? If you asked me last week, I would have said that const in general is just a “safety net” to protect you from yourself. I would have proceeded to babble something about how mutators must be non-const, and accessors should always be const.
I’m sure I would have said “always”.
But last night I realized that, in certain instances, you can optimize by breaking this rule.
Consider the following:
#include
#include
using namespace std;
template < class T >
class Foo {
private:
bool initialized_;
T data_;
public:
Foo() :
initialized_( false )
{}
Foo( T data ) :
initialized_( true ),
data_( data )
{}
void set( int data )
{
data_ = data;
initialized_ = true;
}
void fubarize()
{
if( !initialized_ )
throw invalid_argument( “Can’t fubarize an uninitialized Foo” );
cout << "Called slow fubarize()" << endl;
}
void fubarize() const
{
cout << "Called fast fubarize()" << endl;
}
};
int main()
{
Foo f;
try
{
f.fubarize();
}
catch( invalid_argument &e )
{
cout << e.what() << endl;
}
f.set( 42 );
f.fubarize();
const Foo cf( 42 );
cf.fubarize();
}
If I compile and execute this program I get the following output:
Can't fubarize an uninitialized Foo Called slow fubarize() Called fast fubarize()
Both versions of fubarize() are conceptually const — neither changes state. However, thanks to the fact that C++ overload resolution takes constness into consideration, we can provide two different versions: one for regular Foo objects and an ”optimized” version for const Foo objects. Because const Foo objects must be initialized upon construction, we can omit the initialized_ check.
It’s always faster to do less work.
This is pretty neat. However, it’s unfortunate that I have to trade the compiler’s “const” enforcement for this optimization.

I’ll bet that mutable will cause some serious problems with most compilers if there are global const objects - they’ll get stuck in read only data, and then the mutable member will make the code puke :-)
Kev,
I just tried a global static const object with a mutable member, and it seems to work. I also tried the old const_cast(this) trick, and that works too.
Either you guys are smart enough to detect the const_cast, or you never stick static const global objects into rdata.
I’m guessing the latter.
I’ll probably make a blog post out of this…one day :)
-Mark
Beware premature optimization.
You are sacrificing semantic clarity for a false optimization. Just because you *can* do something doesn’t mean you should.
BTW, your example is flawed; I can clearly write this (nothing prevents misuse):
const Foo f;
f.fubarize(); // “Called fast fubarize” but it wasn’t initialized (and can never be)!
The bigger problem I have is that your example isn’t using an accessor: it returns void! A better example may be a lazy initialization example (ala caching database queries), but all you save is the query and I’d still do that by making the cached value mutable.
A better optimization for your case is to remove the default constructor for Foo and force clients to provide a valid value!
Bheesh,
The backstory here is that I spent a few weeks building a perf test harness around boost::function. It’s working great now, but the overhead (measuring the perf of a call to a “null” function) is too high. I started looking into what was happening when you invoke operator()() on a const boost::function object, and it turns out that there is an “if(!initialized)” check very similar to this example.
So I think it’s mature optimization :) But I guess that’s not really the point.
As you point out, I totally missed the fact that you can still create an uninitialized const Foo. I think that’s the major problem here.
What I want is a class which can be instantiated as both const and non-const, but where all const objects must use the 2nd constructor (the one which sets data_ and initialized_ to true).
Unfortunately, I don’t know of a way to do this. Maybe I need a FooFactory or something? I’ll have to think about this for a while.
-Mark
I forgot to mention the most common genuine use of this feature: returning iterators.
Calling begin() on a const vector returns a const_iterator and calling begin() on a non-const vector returns a plain-old iterator.
That’s overload resolution I can get behind!