C++ Templates, avoiding virtual

I’ve not done a great deal with C++ Templates, and especially with some of the improvements in C++0x, I’m thinking I’d like to change that.

One particular example I’m tinkering with right now is a database abstraction layer I’ve been dragging around with me for years. It has two primary members: DBQuery and DBRow. Up until now they have been littered with #ifdef’s. I want to ditch the ifs and switch to templates.

Lets assume the basic definition is this (it may not be valid C++, I’m not going to check ;-P)

class DBRow
{
private:
  db::row_t m_row ;  // The row data, in db-specific form
  unsigned int m_columns ;  // Number of columns
  unsigned int m_nextColumn ;  // Cursor

public:
  DBRow(UINT32 numColumns, db::result_t result) ; // Defined elsewhere

  const char* operator[](unsigned int index) const
  {
    if ( index >= m_columns )
      throw std::runtime_error("Invalid index") ; // or something
#if DBQUERY_USING == DB_MYSQL
    return m_row[index] ;
#elseif DBQUERY_USING = DB_SQLITE3
    return (const char*)sqlite3_column_blob(m_row, index) ;
#else
    assert( !"Unsupported database type" ) ;
#endif

  };

  const char* GetNextText()
  {
    if ( m_nextColumn >= m_columns ) return NULL ;
    return this->operator[](m_nextColumn++) ;
  }

  unsigned int GetNextUInt()
  {
    if ( m_nextColumn >= m_columns ) return 0 ;
#if DBQUERY_USING == DB_MYSQL
    const char* source = GetNextText() ;
    return (source != NULL ? strtoul(source, NULL, 10) : 0) ;
#elseif DBQUERY_USING == DB_SQLITE3
    return (unsigned int)sqlite3_column_int(m_row, m_nextColumn++) ;
#else
    assert( !"Unsupported database type" ) ;
#endif
  }

  signed int GetNextSInt()
  {
    if ( m_nextColumn >= m_columns ) return 0 ;
#if DBQUERY_USING == DB_MYSQL
    const char* source = GetNextText() ;
    return (source != NULL ? strtoul(source, NULL, 10) : 0) ;
#elseif DBQUERY_USING == DB_SQLITE3
    return (signed int)sqlite3_column_int(m_row, m_nextColumn++) ;
#else
    assert( !"Unsupported database type" ) ;
#endif
  }

  ...
}

Ugh. That’s not the actual code, but it’s also not that far off =(

A whole bunch of that could be cleaned up with some basic templating. But that seems to get in the way of doing more advanced templating.

Firstly: Every now and again I find a website that describes how to use templating/traits to provide automatic and meaningful compiler errors. But I can never find the damn thing when I’m looking for it.

I’d like to try and do this as cleanly as possible, without making it impossible to read the code to ascertain the API (there’s actually a ton of documentation in it, but still).

So I started out trying to simple template DBRow…

template<struct _DbType>
class DBRow
{
...
public:
  const char* operator[](unsigned int index) const
  {
    if ( index >= m_columns )
      thtrow std::invalid_argument("index out of bounds") ;
    return _getColumn(index) ;
  }
...
protected:
  template<MYSQL> const char* _getColumn(unsigned int index) const
  {
    return m_row[index] ;
  }
...
...
} ;

Well that’s just plain wrong; and GCC doesn’t like it because it’s an explicit specialization, which apparently has to be declared outside of the class definition.

And also, it doesn’t provide nice, cosy, compile-time warnings about attempting to create a DBRow or something.

After fiddling around with variations, I decided to sub-class the type-specific members.

struct MySQL
{
  struct db_t {} ;
  typedef char** row_t ;
} ;
struct SQLite
{
  struct db_t {} ;
  typedef sqlite3_stmt* row_t ;
} ;

template<typename _DbType>
class DBRowAccess
{
  typedef _DbType::row_t row_t ;
public:
  DBRowAccess() {}
  const char* _getColumn(row_t& row, unsigned int index) { assert( "!Unsupported database type" ) ;
  template<typename _ReturnType> _ReturnType _getInt(row_t& row, unsigned int index) ;
} ;

template<>
class DBRowAccess<MySQL>
{
  const char* _getColumn(row_t& row, unsigned int index)
  {
    return row[index] ;
  }
  template<typename _ReturnType> _ReturnType _getInt(row_t& row, unsigned int index)
  {
    const char* source = _getColumn(row, index) ;
    return (_ReturnType) ( source != NULL ? strtoul(source, NULL, 10) : 0 ) ;
  }
} ;

template<>
class DBRowAccess<SQLite>
{
  const char* _getColumn(row_t& row, unsigned int index)
  {
    return sqlite3_column_blob(row, index) ;
  }

  template<typename _ReturnType> _ReturnType _getInt(row_t& row, unsigned int index)
  {
    return (_ReturnType) sqlite3_column_int(row, index) ;
  }
} ;

template<typename _DbType>
class DBRow : protected DBRowAccess<_DbType>
{
  _DbType::row_t m_row ;
...
public:
  const char* operator[](unsigned int index) const
  {
    if ( index >= m_columns )
      thtrow std::invalid_argument("index out of bounds") ;
    return _getColumn(index) ;
  }
...
protected:
  template<MYSQL> const char* _getColumn(unsigned int index) const
  {
    return m_row[index] ;
  }
...
...
} ;

Oh, I’m getting nowhere fast aren’t I? :) And I’m not entirely sold on the base class concept.

Right. Time to try fiddling with type traits. Lets see if I can figure that out.

// Convenience definitions incase you want to try this.
typedef struct st_mysql {} MYSQL ;
struct MYSQL_RES {} ;
struct sqlite3 {} ;
struct sqlite3_stmt {} ;

struct MySQL {} ;   // Note: Lowercase 'y'
struct SQLite {} ;  // Note: Uppercase SQL

// Traits-types for determining whether or not a db type is supported.
struct db_type_unsupported { } ;
struct db_type_supported { enum { supported = true } ; } ;

// For anything we don't recognize.
template<class _DbType> struct db_type_traits : public db_type_unsupported {} ;

// MySQL database traits
template<> struct db_type_traits<MySQL> : public db_type_supported
{
  struct db_t {} ;
  typedef MYSQL_RES result_t ;
  typedef char** row_t ;
} ;

// SQLite3 database traits
template<> struct db_type_traits<SQLite> : public db_type_supported
{
  struct db_t {} ;
  typedef sqlite3_stmt result_t ;
  typedef sqlite3_stmt* row_t ;
} ;

// Row access for a supported database type.
template<class _DbType, bool = db_type_traits<_DbType>::supported >
struct DBRowAccess
{
  typedef typename db_type_traits<_DbType>::row_t row_t ;
public:
  DBRowAccess() {}
} ;

int
main(int argc, char* argv[])
{
  DBRowAccess<MySQL> mysqlRow ;
  DBRowAccess<SQLite> sqliteRow ;
  DBRowAccess<int> failRow ;

  return 0 ;
}

Finally, some progress!

test.cpp: In function ‘int main(int, char**)’:
test.cpp:85: error: ‘supported’ is not a member of ‘db_type_traits<int>’
test.cpp:85: error: template argument 2 is invalid
test.cpp:85: error: invalid type in declaration before ‘;’ token

Ok – it’s a little wordier than need be, but at least there are words of significance in the output (“supported”, “not”, “db_type_traits”, “int”).

I suppose at this point I might as well put the db-specific member functions into the traits class, since they don’t need to be member functions. But that seems a little bit naughty? And I still have to figure out how I am going to write the template-ized members without hating myself.

With a little extra work, I manage to roll it together into something that works. Note that I’ll probably split some of this out into headers and such to keep the readability higher. Also, there are a bunch of prototypes that I’ve included purely so that you can try this yourself. Compile with “TEST_INVALID_TYPE” defined if you want to see the error output from trying to use an invalid type.

#include <stdio.h>
#include <stdexcept>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

using namespace std ;

typedef struct st_mysql {} MYSQL ;
struct MYSQL_RES {} ;
struct sqlite3 {} ;
struct sqlite3_stmt {} ;

// Traits-types for determining whether or not a db type is supported.
struct db_type_unsupported { } ;
struct db_type_supported { enum { db_type_is_supported = true } ; } ;

struct MySQL : public db_type_supported // Note: Lowercase 'y'
{
	typedef MYSQL handle_t ;
	typedef MYSQL_RES result_t ;
	typedef char** row_t ;

	static const char* GetColumn(const row_t& row, unsigned int index)
	{
		return row[index] ;
	}
	static signed long GetInt(const row_t& row, unsigned int index)
	{
		const char* source = GetColumn(row, index) ;
		return (source != NULL ? strtol(source, NULL, 10) : 0) ;
	}
	static signed long long GetInt64(const row_t& row, unsigned int index)
	{
		const char* source = GetColumn(row, index) ;
		return (source != NULL ? strtoll(source, NULL, 10) : 0) ;
	}
	static float GetFloat(const row_t& row, unsigned int index)
	{
		const char* source = GetColumn(row, index) ;
		return (source != NULL ? strtof(source, NULL) : 0) ;
	}
	static double GetDouble(const row_t& row, unsigned int index)
	{
		const char* source = GetColumn(row, index) ;
		return (source != NULL ? strtod(source, NULL) : 0) ;
	}
} ;

const char* sqlite3_column_blob(sqlite3_stmt*, unsigned int) { return "column" ; }
signed long sqlite3_column_int(sqlite3_stmt*, unsigned int) { return 1 ; }
signed long long sqlite3_column_int64(sqlite3_stmt*, unsigned int) { return 2LL ; }
double sqlite3_column_float(sqlite3_stmt*, unsigned int) { return 3.0f ; }

class SQLite : public db_type_supported // Note: Uppercase SQL
{
public:
	typedef sqlite3 handle_t ;
	typedef sqlite3_stmt result_t ;
	typedef sqlite3_stmt* row_t ;

public:
	static const char* GetColumn(row_t& row, unsigned int index)
	{
		return sqlite3_column_blob(row, index) ;
	}
	static signed long GetInt(row_t& row, unsigned int index)
	{
		return sqlite3_column_int(row, index) ;
	}
	static signed long long GetInt64(row_t& row, unsigned int index)
	{
		return sqlite3_column_int64(row, index) ;
	}
	static float GetFloat(row_t& row, unsigned int index)
	{
		return (float)sqlite3_column_float(row, index) ;
	}
	static double GetDouble(row_t& row, unsigned int index)
	{
		return sqlite3_column_float(row, index) ;
	}
} ;

// Row access for a supported database type.
template< class _DbType, bool = _DbType::db_type_is_supported > struct DBRow { } ;

// Row access for known/supported database types.
template<class _DbType>
class DBRow<_DbType, true>
{
protected:
	typedef _DbType  db_type ;
	typedef typename _DbType::row_t row_t ;

protected:
	// The dataset we're operating on.
	row_t			m_row ;
	// The number of columns.
	unsigned int	m_columns ;
	// Cursor.
	unsigned int	m_nextColumn ;

public:
	DBRow(unsigned int columns, row_t row) : m_row(row), m_columns(columns), m_nextColumn(0) {}

	// Return the string value of a given column with bounds checking.
	const char* operator[](unsigned int index) const
	{
		if ( index >= m_columns )
			throw std::invalid_argument("Out-of-bounds column access") ;
		return db_type::GetColumn(m_row, index) ;
	}

public:
	unsigned int Columns() const { return m_columns ; }
	unsigned int CurrentColumn() const { return m_nextColumn ; }
	bool AtEndOfRow() const { return m_nextColumn >= m_columns ; }

private:
	// Returns the next column to be indexed, and advances the cursor.
	unsigned int _useCursor()
	{
		if ( AtEndOfRow() )
			throw std::logic_error("Attempted to advance past end of row") ;
		return m_nextColumn++ ;
	}

public:
	// Get the next column as a char array: may return NULL.
	const char* NextColumn()
	{
		return db_type::GetColumn(m_row, _useCursor()) ;
	}

	// Get the next column as a char array with empty-string instead of NULL.
	const char* NextString()
	{
		const char* str = NextColumn() ;
		return ( str != NULL ? str : "" ) ;
	}

	const char* NextString(char* destination, size_t maxLen)
	{
		assert( maxLen > 0 || !"maxLen must be at least 1 (for null byte)" ) ;
		strncpy(destination, NextString(), maxLen - 1) ;
		destination[maxLen - 1] = 0 ;
		return destination ;
	}

	// Get the next integer
	unsigned int NextUInt() { return (unsigned int)db_type::GetInt(m_row, _useCursor()) ; }
	  signed int NextSint() { return   (signed int)db_type::GetInt(m_row, _useCursor()) ; }

	// Get the next 64 bit integer
	unsigned long long NextUInt64() { return (unsigned long long)db_type::GetInt64(m_row, _useCursor()) ; }
	  signed long long NextSInt64() { return   (signed long long)db_type::GetInt64(m_row, _useCursor()) ; }

	// Get the next float/double
	double NextDouble() { return db_type::GetDouble(m_row, _useCursor()) ; }
	 float NextFloat()  { return db_type::GetFloat(m_row, _useCursor()) ; }
} ;

int
main(int argc, char* argv[])
{
	const char* myRow[] = { "hello", "world", "42" } ;
	SQLite::row_t sqRow = NULL ;

	DBRow<MySQL> mysqlRow(3, (MySQL::row_t)myRow) ;
	DBRow<SQLite> sqliteRow(0, sqRow) ;
#ifdef TEST_INVALID_TYPE
	DBRow<int> failRow ;
#endif

	try
	{
		printf("Invalid operator[] access:\n") ;
		mysqlRow[5] ;			// Should throw exception
		assert( !" Should have gotten an exception from mysqlRow[5]" ) ;
	}
	catch ( std::invalid_argument& e )
	{
		printf("OK: Got expected exception from mysqlRow[5]: %s\n", e.what()) ;
	}
	catch ( std::exception& e )
	{
		printf("FAIL: Unexpected exception from mysqlRow[5]: %s\n", e.what()) ;
	}

	try
	{
		printf("Valid operator[] access:\n") ;
		const char* p = mysqlRow[2] ;		// Should be '42'
		assert( strcmp(p, "42") == 0 && "Expected to find '42' in column 42." ) ;
		printf("OK\n") ;
	}
	catch ( std::exception& e )
	{
		printf("FAIL: Unexpected exception from mysqlRow[2]: %s\n", e.what()) ;
	}

	try
	{
		printf("Invalid NextColumn access:\n") ;
		sqliteRow.NextColumn() ;	// Should throw exception
		assert( !"Should have gotten an exception from sqliteRow.GetNextColumn()" ) ;
	}
	catch ( std::logic_error& e )
	{
		printf("OK: Got expected exception from sqliteRow.GetNextColumn(): %s\n", e.what()) ;
	}
	catch ( std::exception& e )
	{
		printf("FAIL: Unexpected exception from sqliteRow.GetNextColumn(): %s\n", e.what()) ;
	}

	// Lets see if we can read the MySQL row properly.
	try
	{
		printf("First string [hello]:\n") ;
		const char* hello = mysqlRow.NextString() ;
		assert( strcmp(hello, "hello") == 0 && "Expected first mysqlRow.NextString to return 'hello'" ) ;
		printf("OK\n") ;

		printf("Second string [world->wor]:\n") ;
		char wor[4] ;
		mysqlRow.NextString(wor, sizeof(wor)) ;
		printf("[%s]\n", wor) ;
		assert( strcmp(wor, "wor") == 0 && "Expected NextString(wor, sizeof(wor)) to return 'wor'" ) ;
		printf("OK\n") ;

		printf("Third column, integer [42]:\n") ;
		unsigned int ltuae = mysqlRow.NextUInt() ;
		assert( ltuae == 42 && "Life the universe and everything is supposed to be 42" ) ;
		printf("OK\n") ;
	}
	catch ( std::exception& e )
	{
		printf("FAIL: Got unexpected exception from mysqlRow: %s\n", e.what()) ;
	}

	return 0 ;
}

17 Comments

I suppose another way to do the above would be to make the functions like GetColumn etc part of db_type_supported and then specialize the db-type specific members outside the struct definition.

Now I have to figure out how to achieve polymorphism so I can say:

Database<MySQL> db ;

void connectToDB()
{
  db.Connect(/* connection parameters*/) ;
}

void readPlayer()
{
  Query q = db.Query(/* query */) ;
  while ( q.FetchRow() )
  {
    Row& r = q.Row() ;
    int id = r.NextUInt() ;
    const char* name = r.NextString() ;
    /* ... */
  }
}

Because that won’t work with my current model, since DBQuery is parallel to, not derived from, DBQuery etc.

Right now you’d have to do

Database<MySQL> db ;
...
  DBQuery<MySQL> query(...) ;
  ...
    DBRow<MySQL>& row = query.Row() ;

I could just embed a Query into every database connection, since they are single threaded. But I want a nice, clean way to make sure that when a query goes out of scope it gets cleaned up.

Shame on me. I forgot you can’t do this.

header.h:

template<typename _type>
class Foo
{
  Foo() ;    // Constructor declared elsewhere
} ;

source1.cpp:

#include "header.h"

int main(int argc, const char* argv[])
{
  Foo foo ;
}

source2.cpp:

#include “header.h”

template
Foo::Foo()
{
}

What I really want is a mix of templates and polymorphism. But I don’t want to have to put all of my implementation into the header files — as required by templates.

Urgle.

“What I really want is a mix of templates and polymorphism. But I don’t want to have to put all of my implementation into the header files — as required by templates.”

You don’t *have* to put the implementation in a header file, you can implement and then explicitly instantiate all templates you know that you’ll use in a file and compile that and then link it in. Just make sure you remember that’s what you’ve done when the compiler complains that it can’t find a definition… ;-)

Also, is there some reason you don’t want to use runtime polymorphism? It seems the first example could be very smoothly converted into a case with an abstract base class DBRow and then two derived classes for sqlite and mysql? Are you trying to avoid the calling overhead of the virtuals?

There’s also the “curiously recursive template pattern” (http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern) to do static polymorphism.

I can’t help but think that advanced template work is kind of… overkill.


// db.h
template
struct DBOperations_ ;

template
struct DBData_ ;

// It's all determined at compile-time via a define anyways...
typedef DBData_ DBData ;
typedef DBOperations_ DBOperations ;

#if DBQUERY_USING == DB_MYSQL
#include "db_mysql.h"
#else
// ...
#endif


// db_mysql.h
template
struct DBData_
{
db::row_t m_row ;
} ;

template
struct DBOperations_ {
static const char* GetIndex(size_t index, const DBData_& data)
{
return data.m_row[index] ;
}

static unsigned int GetNextUInt(const DBData_&)
{
const char* source = GetNextText() ;
return (source != NULL ? strtoul(source, NULL, 10) : 0) ;
}
} ;

// NOTE: As mentioned by someone else, although not a part of the standard
// you probably can make due with just the definitions via something like:
extern template class DBData_ ;
extern template class DBOperations_ ;


class DBRow
{
private:
DBData m_data ; // DB-specific information.
unsigned int m_columns ; // Number of columns
unsigned int m_nextColumn ; // Cursor

public:
DBRow(UINT32 numColumns, db::result_t result) ;

const char* operator[](unsigned int index) const
{
if ( index >= m_columns )
throw std::runtime_error("Invalid index") ;
return DBOperations::GetIndex(index, m_data) ;
};

const char* GetNextText()
{
if ( m_nextColumn >= m_columns ) return NULL ;
return this->operator[](m_nextColumn++) ;
}

unsigned int GetNextUInt()
{
if ( m_nextColumn >= m_columns ) return 0 ;
return DBOperations::GetNextUInt(m_data) ;
}

// ...
} ;

Truth be told, even that seems like a bit much: The data and operations probably don’t need to be separate — a DBImplementation class or the like seems more appropriate and would avoid the awkward passing around of m_data — and something like this would probably do just as well:


#if DB_TYPE == DB_MYSQL
typedef MYSQLData DBData ;
typedef MYSQLOperations Operations ;
#else
// ...
#endif

As much as I like my templates, I really don’t think this calls for anything that complex (at least with the sample given).

Gah, damned code tags. Where’s a preview button when you need it?…

So – mostly this is an exercise. My DB API supports several databases, but not simultaneously. I want to avoid DB awareness, so that you don’t have to know what database opened a connection, you just start using the higher-level interface to talk to a given handle and voila. Obviously, that means that the “connection” class has to have most of the functionality.

Yeah, I wanted to avoid the overhead of virtuals, since it would produce very clean executable code not littered with conditional branches and such that, in most cases, would be unnecessary. But when a particular piece of code needed to translate between two databases, it would be able to do so easily.

Mostly I wanted to try a different approach – it’s too easy to get comfortable and set in your ways with coding :)

The main difficulty is that each database API is subtly different in significant ways. For instance, MySQL rows are char**, whereas with SQLite you don’t get rows, you have a pointer to a statement which you query columns from.

With templates, you can use typedefs to avoid having to manually encapsulate each element of the API. Without templates, you wind up with quite a lot of virtuals, too many for the case where a particular app is only using one database.

You don’t *have* to put the implementation in a header file, you can implement and then explicitly instantiate all templates you know that you’ll use in a file and compile that and then link it in. Just make sure you remember that’s what you’ve done when the compiler complains that it can’t find a definition… ;-)

I actually tried that with my example, where it came apart was trying to implement generic member functions, like a common constructor, which is because my common constructor wasn’t quite as common as I wanted to think. It references members of db_type::… so the compiler can’t find it:

namespace DB
{
  template<typename _DbType>
  Query<_DbType>::Query(...)
    : ... initializers ...
  {
    .. some stuff ..
    db_type::something();
    .. more stuff ..
  }
}

If I have to have a copy of the constructor per db type, that’s a lot of additional code to maintain that introduces a lot of potential for errors :(

I could turn it around and move the constant parts of the constructors into templated functions, and then make short per-db constructors that call them, but that’s still duplication :)

It’s also more than that. Consider; if I create a set of abstract types for encapsulating the database handle, the result set and the current row.

struct IHandle { } ;
struct IResult { } ;
struct IRow { } ;
struct Credentials { /* DB Parameters */ } ;

I don’t know what they are going to contain, so I can’t do the following:

// Base class for a database interface.

class IDatabase
{
public:
  IDatabase() {}
  virtual ~IDatabase() {}

  virtual bool Connect(const Credentials&) = 0 ;

protected:
  IHandle   m_handle ;
  IResult   m_result ;
  IRow      m_row ;
} ;

Instead I either have to take pointers, or I have to use virtual functions to return those, and that gets ugly and really starts to up the overhead.

BTW; I know I’m reinventing the wheel: there are plenty of DB APIs out there. I’ve used quite a few of them :) I’m not trying to create a replacement, I’m just trying to write one – IYSWIM :)

The most common approach, like perl’s DBI, is to have a base class that encapsulates the connection and everything below it. You call it to connect, you call it to execute SQL, you call it to retrieve rows from a result set.

The disadvantage to this method is that you have to keep going through the base class, which then has to keep performing sanity checks. For instance – if you’re trying to retrieve a column from a row, it has to make sure there’s a row available.

Now you could create interface classes, like an abstract row class that always just calls Connection->Function()… But that’s a lot of glue, and I want to avoid a lot of manual glue creation/maintenance.

I also want scoped protection. So, for instance, when you perform a query I want to create a scoped object that will release the query results as soon as you are done with it, and likewise for the Row. (This is what I currently have with my API).

Templates provide a nice mechanism for dealing with this forward abstraction.

“I actually tried that with my example, where it came apart was trying to implement generic member functions, like a common constructor, which is because my common constructor wasn’t quite as common as I wanted to think. It references members of db_type::… so the compiler can’t find it:”

Well, half true: If a type’s declaration isn’t explicitly specialized (i.e., the class’ members don’t change), then it can still be explicitly instantiated without any problems:

template
class Type {
static void Function() ;
// ...
} ;
// ...
void Foo() {
Type::Function() ;
Type::Function() ;
}

Elsewhere…

// "Normal" version.
template
void Type::Function() {
// ...
}
// Specialized version.
template
void Type::Function() {
// ...
}
// Instantiate the types to avoid linker errors.
template class Type ;
template class Type ;

Granted, if you don’t *want* a part of the type to be instantiated, you’ll have to do the instantiation per-function instead, but as long as the basic declaration doesn’t change, nothing needs to go in a header.

Goddamn braces… One of the Foo() calls is a float and the other’s an int, and one of the Type::Function() definitions is for type “T” and the other is explicitly int.
The point is that, at the “template class Foo&ltint&gt” and “template class Foo&ltfloat&gt”, the functions are defined based on what the code can see at that point — meaning the linker won’t complain that it doesn’t know what it supposed to do.

Hehe, the syntax for source markup is
[ sourcecode language=’c’ ]

[ /sourcecode ]

without the leading/trailing space, obviously.

I’m currently trying a different approach; I basically have a fairly simple abstract “Connection” base class, which has lots of protected accessors, e.g. operator>>s.

Then I have a “Result” class which is just a class with a lot of member functions, the constructor of which is private-friend-Connection; ergo you can only get one from the Connection class. This provides me a scoping mechanism to ensure result sets are cleanedup when you are done with them.

The Result class provides public operator>>s and, via a template, to the Connection operator>>s. Also provides the row-iteration code, again via the Connection protected members.

That helps me limit the amount of sanity checking per-operation and when compiled with optimization, all the indirection is eliminated.

MySQLConnection mydbh ;
...
{
  ...
  mydbh.Connect(credentials) ;
  ...
}

...

{
  try
  {
    mydbh.Query("SELECT id, name, last_login FROM players") ;

    // Constructor will fail if no results.
    DB::ResultSet rs(mydbh) ;

    int id ;
    char* name, lastLogin ;
    while ( rs.FetchRow() )
    {
      rs >> id >> name >> lastLogin ;
      printf("%08u:%s:%s\n", id, name, lastLogin) ;
    }
  }
  catch ( std::exception& e )
  {
    fprintf(stderr, "OMG WE HAZ ERRAH! %s", e.what()) ;
  }

P.S. So funny to see other coders putting spaces infront of semis :)

[quote]P.S. So funny to see other coders putting spaces infront of semis :)[/quote]
Heh, I figured I’d give it a try and it stuck, at least for now: Something about a clear “statement over” works well with my brain when I’m reading code. I’m still not putting the leading brace on its own line, though: I have my limits. :p

Hehe, the brace thing is funny too. It’s easy to justify putting it on a new line: braces scan vertically, makes them real easy to spot, rather than diagonally. It’s easy to counter: the source code becomes an extra line longer.

Popular argument against statement { is that long lines make the trailing { hard to spot. The counter? Don’t write long lines, stupid :)

I found that it was following that which lead me to putting the braces on the next line anyway. It was more of a pain when using VI, but when you have a decent gui IDE with a collapse feature, it’s not really that bad.

To be honest – I’ve found that the coding style I use works best with clean code. 3000 line functions don’t really benefit from it, they just get longer. But if you work on writing clear code in the first place, it actually lends itself to it really well. Small functions written with the brace-following-line and etc become very scannable and readable.

Well for me it’s more a brain issue: I have a really spotty short-term memory, so my eyes subconsciously jump up and down the page to try to remember what I read two seconds before and put it together with what I read just now. Combine this with difficulties in pinpointing what line I’m on and you end up with a lot of trouble when there are too many disconnected chunks of code.
I do a lot better with horizontal spacing though (thus, I suppose, liking the space before the semicolon), so indentation serves me really well — I do a lot better with lots of if/while statements than long strings of simple statements. As a result, my functions end up being very fine-grained with the larger function being mostly the controlling logic, and stretching those out just makes it harder for me to deal with.

Leave a Reply

Name and email address are required. Your email address will not be published.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

You may use these HTML tags and attributes:

<a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <pre> <q cite=""> <s> <strike> <strong> 

%d bloggers like this: