В C ++ возможно ли определить функцию с изменяющимся блоком кода, которая будет выполняться и передаваться через другую функцию? - PullRequest
1 голос
/ 01 декабря 2019

У меня есть класс, обрабатывающий запросы SQL (он использует функции Qt, но я думаю, что это не имеет значения). Все запросы, которые записывают данные, имеют точно такой же базовый фрейм, который выглядит следующим образом:

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;
}

Можно ли повторно использовать этот код, чтобы его не нужно было определять для каждой функции? Я думал об использовании функции, которая будет вызываться с указателем на функцию, содержащую изменяющийся код, но сигнатура для каждой функции различна, и поэтому мне пришлось бы определять перегруженную для каждого случая;Я также думал об использовании препроцессора для генерации функции, но это та же проблема с другим числом и типом аргументов.

Может ли блок кода, который должен быть выполнен, передаваться другой функции? Или это можно сделать с помощью шаблона функции?

Редактировать: Вот как это можно реализовать:

В заголовке:

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;
}

И пример функции, использующей его:

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);
    });
}

Редактировать 2: То же самое может быть достигнуто без шаблонов:

Используя std::function, лямбда, определенная вФактическая функция может быть просто передана без использования шаблонов. Тогда реализация вспомогательной функции выглядит следующим образом:

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;
}

В любом случае, очевидно, лучше использовать шаблонный подход, так как в этом случае компилятор сгенерирует необходимые функции во время сборки и сможет оптимизировать,тогда как он не знает, что на самом деле будет сделано с использованием подхода std :: function, так как вызовы происходят во время выполнения.

1 Ответ

2 голосов
/ 01 декабря 2019

Лямбда-замыкания, похоже, то, что вам нужно.
https://en.cppreference.com/w/cpp/language/lambda

В качестве примера, универсальные алгоритмы очень похожи на ваш вариант использования: общий алгоритм, который не меняется, и некоторые небольшие части, которыемогут быть свободно предоставлены при вызове.
https://en.cppreference.com/w/cpp/header/algorithm

Не зная точных подробностей ожидаемого, я приведу здесь тривиальный пример:

#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;
}

Даже если подписьлямбда-замыкания всегда одинаковы, захват можно рассматривать как обходной путь для предоставления дополнительных параметров.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...