Дублировать код с помощью С++ 11

В настоящее время я работаю над проектом, и у меня есть следующая проблема.

У меня есть метод С++, который я хочу работать двумя разными способами:

void MyFunction()
{
  foo();
  bar();
  foobar();
}

void MyFunctionWithABonus()
{
  foo();
  bar();
  doBonusStuff();
  foobar();
}

И я бы хотел не дублировать мой код, потому что фактическая функция намного длиннее. Проблема в том, что я ни при каких обстоятельствах не должен добавлять время выполнения программы, когда MyFunction вызывается вместо MyFunctionWithABonus. Вот почему я не могу просто иметь логический параметр, который я проверяю с помощью сравнения на С++.

Моей идеей было бы использовать шаблоны С++ для практически дублирования моего кода, но я не могу придумать способ сделать, в котором у меня нет дополнительного времени выполнения, и мне не нужно дублировать код.

Я не эксперт с шаблонами, поэтому я могу что-то пропустить.

Есть ли у вас какая-нибудь идея? Или это просто невозможно в С++ 11?

Ответ 1

С шаблоном и лямбда вы можете:

template <typename F>
void common(F f)
{
  foo();
  bar();
  f();
  foobar();
}

void MyFunction()
{
    common([](){});
}

void MyFunctionWithABonus()
{
  common(&doBonusStuff);
}

иначе вы можете просто создать функцию prefix и suffix.

void prefix()
{
  foo();
  bar();
}

void suffix()
{
    foobar();
}

void MyFunction()
{
    prefix();
    suffix();
}

void MyFunctionWithABonus()
{
    prefix();
    doBonusStuff();
    suffix();
}

Ответ 2

Что-то вроде этого будет приятно:

template<bool bonus = false>
void MyFunction()
{
  foo();
  bar();
  if (bonus) { doBonusStuff(); }
  foobar();
}

Позвонить через:

MyFunction<true>();
MyFunction<false>();
MyFunction(); // Call myFunction with the false template by default

"Уродливый" шаблон можно избежать, добавив некоторые полезные обертки к функциям:

void MyFunctionAlone() { MyFunction<false>(); }
void MyFunctionBonus() { MyFunction<true>(); }

Вы можете найти приятную информацию об этом методе там. Это "старая" бумага, но сама по себе техника остается абсолютно верной.

Если у вас есть доступ к хорошему компилятору С++ 17, вы можете даже продвинуть эту технику, используя constexpr, если:

template <int bonus>
auto MyFunction() {
  foo();
  bar();
  if      constexpr (bonus == 0) { doBonusStuff1(); }
  else if constexpr (bonus == 1) { doBonusStuff2(); }
  else if constexpr (bonus == 2) { doBonusStuff3(); }
  else if constexpr (bonus == 3) { doBonusStuff4(); }
  // Guarantee that this function will not compile
  // if a bonus different than 0,1,2,3 is passer
  else { static_assert(false);}, 
  foorbar();
}

Ответ 3

Учитывая некоторые комментарии, сделанные OP относительно отладки, здесь версия, которая вызывает doBonusStuff() для отладочных сборников, но не выпускает сборки (которые определяют NDEBUG):

#if defined(NDEBUG)
#define DEBUG(x)
#else
#define DEBUG(x) x
#endif

void MyFunctionWithABonus()
{
  foo();
  bar();
  DEBUG(doBonusStuff());
  foobar();
}

Вы также можете использовать макрос assert, если хотите проверить условие и выполнить сбой, если оно ложно (но только для отладочных сборников, выпускные сборки не будут выполнять проверку).

Будьте осторожны, если doBonusStuff() имеет побочные эффекты, так как эти побочные эффекты не будут присутствовать в сборках релизов и могут аннулировать допущения, сделанные в коде.

Ответ 4

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

void callBonus() {}

template<typename F>
void callBonus(F&& f) { f(); }

template <typename ...F>
void MyFunction(F&&... f)
{
  foo();
  bar();
  callBonus(std::forward<F>(f)...);
  foobar();
}

Код вызова:

MyFunction();
MyFunction(&doBonusStuff);

Ответ 5

Другая версия, использующая только шаблоны и не перенаправляющие функции, поскольку вы сказали, что не хотите накладных расходов во время выполнения. Поскольку я беспокоюсь, это увеличивает время компиляции:

#include <iostream>

using namespace std;

void foo() { cout << "foo\n"; };
void bar() { cout << "bar\n"; };
void bak() { cout << "bak\n"; };

template <bool = false>
void bonus() {};

template <>
void bonus<true>()
{
    cout << "Doing bonus\n";
};

template <bool withBonus = false>
void MyFunc()
{
    foo();
    bar();
    bonus<withBonus>();
    bak();
}

int main(int argc, const char* argv[])
{
    MyFunc();
    cout << "\n";
    MyFunc<true>();
}

output:
foo
bar
bak

foo
bar
Doing bonus
bak

Теперь существует только одна версия MyFunc() с параметром bool в качестве аргумента шаблона.

Ответ 6

Вы можете использовать отправку тегов и простую функцию перегрузки:

struct Tag_EnableBonus {};
struct Tag_DisableBonus {};

void doBonusStuff(Tag_DisableBonus) {}

void doBonusStuff(Tag_EnableBonus)
{
    //Do bonus stuff here
}

template<class Tag> MyFunction(Tag bonus_tag)
{
   foo();
   bar();
   doBonusStuff(bonus_tag);
   foobar();
}

Это легко прочитать/понять, можно развернуть без пота (и без шаблонов if), добавив больше тегов), и, конечно же, не оставит след на рабочем столе.

Вызывающий синтаксис довольно дружелюбен, но, конечно, может быть завернут в ванильные вызовы:

void MyFunctionAlone() { MyFunction(Tag_DisableBonus{}); }
void MyFunctionBonus() { MyFunction(Tag_EnableBonus{}); }

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