1

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.

Tobias Leupold
  • 1,512
  • 1
  • 16
  • 41

1 Answers1

2

Lambda closures seem to be what you need.
https://en.cppreference.com/w/cpp/language/lambda

As an example, generic algorithms are very similar to your use case: a common algorithm which does not change and some small parts that can be freely provided at the invocation.
https://en.cppreference.com/w/cpp/header/algorithm

Without knowing the exact details of what is expected, I provide here a trivial example:

#include <iostream>

template<typename Fnct>
void
common_part(Fnct fnct)
{
  std::cout << "begin common part\n";
  for(auto i=0; i<3; ++i)
  {
    std::cout << "iteration " << i << " of common part\n";
    fnct(i);
  }
  std::cout << "end common part\n";
}

void
specific_function_A()
{
  common_part(
    [&](const auto &n)
    {
      std::cout << "specific part A: " << n << '\n';
    });
}

void
specific_function_B(int something_else)
{
  common_part(
    [&](const auto &n)
    {
      std::cout << "specific part B: " << n+something_else << '\n';
      something_else*=2;
    });
}

int
main()
{
  specific_function_A();
  std::cout << "~~~~~~~~\n";
  specific_function_B(100);
  return 0;
}

Even if the signature of the lambda-closure is always the same, the capture can be seen as a workaround to provide additional parameters.

prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • I use lambdas, but only to connect Qt signals to trivial functions … would I define someSqlFunction as a e. g. writeHelper and then pass a reference to a lambda function defined in the respective function that returns the return value of writeHelper? Would you mind to give an example? – Tobias Leupold Dec 01 '19 at 13:22
  • Using std::function, there's no need for templates, see my edit above. Thanks anyway for pushing me in the right direction! – Tobias Leupold Dec 01 '19 at 19:53
  • 1
    @TobiasLeupold Yes it works too, but I have always heard/read that we should avoid using `std::function` as much as possible because it introduces some memory and performance overhead. Item 5 of *Effective modern C++* by Scott Meyers explains this. – prog-fh Dec 01 '19 at 20:15
  • 1
    @TobiasLeupold This question is interesting too https://stackoverflow.com/q/14677997/11527076 – prog-fh Dec 01 '19 at 20:26
  • Oh, thanks about that info! I thought using std::function was the "easier" way, and I didn't think about speed. – Tobias Leupold Dec 02 '19 at 06:40