I have a class handling SQL queries (it uses Qt functions, but I think this doesn't matter). All queries that write data have the exact same base frame that looks like this:
bool Database::someSqlFunction(some variables)
{
if (! startTransaction()) {
return false;
}
QSqlQuery query(m_db);
try {
... Code using the passed variables and query ...
commitTransaction();
} catch (...) {
rollbackTransaction(query);
return false;
}
return true;
}
Is it possible to re-use this code so that it won't have to be defined per function? I thought about using a function to be called with a pointer to the function containing the varying code, but the signature is different for each function and I thus would have to define an overloaded one for each case; I also thought about using the preprocessor to generate the function, but it's the same problem with the different number and type of arguments.
Can a block of code to be executed be passed to another function? Or can this be done via a function template?
Edit: Here's how this can be implemented:
In the header:
template<typename SqlFunction>
bool writeHelper(SqlFunction sqlFunction)
{
if (! startTransaction()) {
return false;
}
QSqlQuery query(m_db);
try {
sqlFunction(query);
commitTransaction();
} catch (...) {
rollbackTransaction(query);
return false;
}
return true;
}
And and example function using it:
bool Database::registerPlayers(const QString &name, int markerId)
{
return writeHelper([&](QSqlQuery &query) {
queryPrepare(query, QStringLiteral("INSERT INTO players(id, name, marker) "
"VALUES(NULL, ?, ?)"));
query.bindValue(0, name);
query.bindValue(1, markerId != 0 ? markerId : SQLITE_NULL);
queryExec(query);
});
}
Edit 2: The same can also be achieved without templates:
Using std::function
, the lambda defined in the actual function can simply be passed without having to use templates. The implementation of the helper function looks like this then:
bool Database::writeHelper(std::function<void(QSqlQuery &query)> sqlFunction)
{
if (! startTransaction()) {
return false;
}
QSqlQuery query(m_db);
try {
sqlFunction(query);
commitTransaction();
} catch (...) {
rollbackTransaction(query);
return false;
}
return true;
}
Anyway, it's apparently better to use the template approach, as in this case, the compiler will generate the needed functions build-time and can optimize, whereas he doesn't know what will be actually done using the std::function approach, as the calls happen run-time.