Friday, November 5, 2010

C++ magic (or, How to make a bug)

I was reading through some old code of mine, something I had written 10 years ago, and came across what I thought must either be a bug I had missed or pure magic.  The essence of what I was looking at can be summarized as:

std::list< std::string > mylist;
std::string * myfunc( const char *t )
{
      mylist.push_front( t );
      std::list< std::string >::iterator i;
      i = mylist.begin();
      return( &(*i) );
}

When I read it, my gut said that push_front should barf because I'm passing a type that is not the same as the list is expecting.

This isn't actually magic, and in fact this sort of loose programming could easily lead to bugs.  The list template is calling a constructor to make it's own locally-scoped string object, and my input just happens to line up to one of the overloaded constructors for std::string class.  Observe similar behavior from the following sample program:

std::list< int > mylist
void buggy_code()
{
        mylist.push_front( 12.456F );
        std::list< int >::iterator i;
        i = mylist.begin();
        std::cout << ( *i ) << std::endl;
}

In both cases, I'm passing a quietly convertible value into the push_front() method.  While the first code would result in correct behavior, the second might easily present a bug.  The lesson to be learned, here, is that while compiler warnings are useful, they don't prevent logic bugs.

No comments: