Optimizing with Const Member Functions

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 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.

Advertisements

6 Responses to “Optimizing with Const Member Functions”


  1. 1 Kevin Frei September 2, 2005 at 2:39 pm

    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 šŸ™‚

  2. 2 Mark September 2, 2005 at 3:53 pm

    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

  3. 3 Bheeshmar September 6, 2005 at 6:35 pm

    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!

  4. 4 Mark September 7, 2005 at 7:45 am

    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

  5. 5 Bheeshmar September 9, 2005 at 8:15 pm

    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!


  1. 1 Const Constructors at mark++ Trackback on July 13, 2007 at 10:14 am
Comments are currently closed.




%d bloggers like this: