Mais rápido `finalmente` para C ++ [fechado]

C ++ até agora (infelizmente) não suportafinally cláusula para umtry declaração. Isso leva a especulações sobre como liberar recursos. Depois de estudar a questão na internet, embora tenha encontrado algumas soluções, não fiquei claro sobre o desempenho deles (e usaria Java se o desempenho não importasse tanto). Então eu tive que comparar.

As opções são:

Baseado em Functorfinally classe proposta emCodeProject. É poderoso, mas lento. E a desmontagem sugere que as variáveis locais da função externa são capturadas de maneira muito ineficiente: empurradas para a pilha uma a uma, em vez de passar apenas o ponteiro do quadro para a função interna (lambda).

RAII: Objeto de limpeza manual na pilha: a desvantagem é a digitação manual e a adaptação para cada local usado. Outra desvantagem é a necessidade de copiar para ele todas as variáveis necessárias para a liberação do recurso.

Específico para MSVC ++__try / __finally declaração. A desvantagem é que obviamente não é portátil.

Criei esse pequeno benchmark para comparar o desempenho em tempo de execução dessas abordagens:

#include <chrono>
#include <functional>
#include <cstdio>

class Finally1 {
  std::function<void(void)> _functor;
public:
  Finally1(const std::function<void(void)> &functor) : _functor(functor) {}
  ~Finally1() {
    _functor();
  }
};

void BenchmarkFunctor() {
  volatile int64_t var = 0;
  const int64_t nIterations = 234567890;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 0; i < nIterations; i++) {
    Finally1 doFinally([&] {
      var++;
    });
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("Functor: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}

void BenchmarkObject() {
  volatile int64_t var = 0;
  const int64_t nIterations = 234567890;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 0; i < nIterations; i++) {
      class Cleaner {
        volatile int64_t* _pVar;
      public:
        Cleaner(volatile int64_t& var) : _pVar(&var) { }
        ~Cleaner() { (*_pVar)++; }
      } c(var);
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("Object: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}

void BenchmarkMSVCpp() {
  volatile int64_t var = 0;
  const int64_t nIterations = 234567890;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 0; i < nIterations; i++) {
    __try {
    }
    __finally {
      var++;
    }
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("__finally: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}

template <typename Func> class Finally4 {
  Func f;
public:
  Finally4(Func&& func) : f(std::forward<Func>(func)) {}
  ~Finally4() { f(); }
};

template <typename F> Finally4<F> MakeFinally4(F&& f) {
  return Finally4<F>(std::forward<F>(f));
}

void BenchmarkTemplate() {
  volatile int64_t var = 0;
  const int64_t nIterations = 234567890;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 0; i < nIterations; i++) {
    auto doFinally = MakeFinally4([&] { var++; });
    //Finally4 doFinally{ [&] { var++; } };
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("Template: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}

void BenchmarkEmpty() {
  volatile int64_t var = 0;
  const int64_t nIterations = 234567890;
  auto start = std::chrono::high_resolution_clock::now();
  for (int64_t i = 0; i < nIterations; i++) {
    var++;
  }
  auto elapsed = std::chrono::high_resolution_clock::now() - start;
  double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
  printf("Empty: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}

int __cdecl main() {
  BenchmarkFunctor();
  BenchmarkObject();
  BenchmarkMSVCpp();
  BenchmarkTemplate();
  BenchmarkEmpty();
  return 0;
}

Os resultados da minha Ryzen 1800X a 3.9Ghz com DDR4 a 2.6Ghz CL13 foram:

Functor: 175148825.946 Ops/sec, var=234567890
Object: 553446751.181 Ops/sec, var=234567890
__finally: 553832236.221 Ops/sec, var=234567890
Template: 554964345.876 Ops/sec, var=234567890
Empty: 554468478.903 Ops/sec, var=234567890

Aparentemente, todas as opções, exceto functor-base (# 1), são tão rápidas quanto um loop vazio.

Existe uma alternativa C ++ rápida e poderosa parafinally, que é portátil e requer cópia mínima da pilha da função externa?

ATUALIZAÇÃO: Comparei a solução @ Jarod42, então aqui na pergunta há código e saída atualizados. Embora como mencionado por @Sopel, ele pode quebrar se a cópia não for executada.

UPDATE2: para esclarecer o que estou pedindo, é uma maneira rápida e conveniente em C ++ de executar um bloco de código, mesmo que uma exceção seja lançada. Pelas razões mencionadas na pergunta, algumas maneiras são lentas ou inconvenientes.

questionAnswers(2)

yourAnswerToTheQuestion