Избежать инструкции if внутри цикла for?

У меня есть класс под названием Writer, который имеет функцию writeVector следующим образом:

void Drawer::writeVector(vector<T> vec, bool index=true)
{
    for (unsigned int i = 0; i < vec.size(); i++) {
        if (index) {
            cout << i << "\t";
        }
        cout << vec[i] << "\n";
    }
}

Я пытаюсь не дублировать код, но все еще беспокоюсь о производительности. В этой функции я выполняю проверку if (index) в каждом раунде моего for -loop, хотя результат всегда один и тот же. Это противоречит "беспокоиться об эффективности".

Я мог бы легко избежать этого, поставив чек вне моего for -loop. Тем не менее, я получу множество дубликатов кода:

void Drawer::writeVector(...)
{
    if (index) {
        for (...) {
            cout << i << "\t" << vec[i] << "\n";
        }
    }
    else {
        for (...) {
            cout << vec[i] << "\n";
        }
    }
}

Итак, это для меня "плохие" решения. То, о чем я думал, это две частные функции, одна из которых выходит из индекса, а затем вызывает другую. Другой только выдает значение. Тем не менее, я не могу понять, как использовать его с моей программой, мне все равно понадобится проверка if, чтобы узнать, какой из них вызывать...

В соответствии с проблемой, полиморфизм кажется правильным решением. Но я не могу понять, как использовать его здесь. Каким будет предпочтительный способ решить эту проблему?

Это не настоящая программа, Мне просто интересно узнать, как решить эту проблему.

Ответ 1

Передайте тело цикла как функтор. Он встраивается во время компиляции, без штрафа за производительность.

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

Если вам разрешено использовать С++ 11, вы можете сделать что-то вроде этого:

#include <iostream>
#include <set>
#include <vector>

template <typename Container, typename Functor, typename Index = std::size_t>
void for_each_indexed(const Container& c, Functor f, Index index = 0) {

    for (const auto& e : c)
        f(index++, e);
}

int main() {

    using namespace std;

    set<char> s{'b', 'a', 'c'};

    // indices starting at 1 instead of 0
    for_each_indexed(s, [](size_t i, char e) { cout<<i<<'\t'<<e<<'\n'; }, 1u);

    cout << "-----" << endl;

    vector<int> v{77, 88, 99};

    // without index
    for_each_indexed(v, [](size_t , int e) { cout<<e<<'\n'; });
}

Этот код не идеален, но вы получаете идею.

В старом С++ 98 он выглядит так:

#include <iostream>
#include <vector>
using namespace std;

struct with_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << i << '\t' << e << '\n';
  }
};

struct without_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << e << '\n';
  }
};


template <typename Func>
void writeVector(const vector<int>& v, Func f) {
  for (vector<int>::size_type i=0; i<v.size(); ++i) {
    f(cout, i, v[i]);
  }
}

int main() {

  vector<int> v;
  v.push_back(77);
  v.push_back(88);
  v.push_back(99);

  writeVector(v, with_index());

  cout << "-----" << endl;

  writeVector(v, without_index());

  return 0;
}

Опять же, код далек от совершенства, но он дает вам эту идею.

Ответ 2

В этой функции я выполняю проверку if (index) в каждом раунде моего цикла for-loop, хотя результат всегда один и тот же. Это противоречит "беспокоиться об эффективности".

Если это действительно так, предсказатель ветвления не будет иметь проблемы с прогнозированием (постоянным) результатом. Таким образом, это приведет лишь к легким накладным расходам для ошибочных предсказаний в первых нескольких итерациях. Это не о чем беспокоиться с точки зрения производительности

В этом случае я выступаю за сохранение теста внутри цикла для ясности.

Ответ 3

Чтобы расширить ответ Али, который является совершенно правильным, но все же дублирует некоторый код (часть тела цикла, к сожалению, вряд ли можно избежать при использовании шаблона стратегии) ​​...

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

Ключ должен использовать способность компилятора выполнять постоянную сворачивание/удаление мертвого кода. Мы можем это сделать, вручную сопоставляя значение времени выполнения index со значением времени компиляции (легко сделать, когда есть только ограниченное число случаев - два в этом случае) и использовать аргумент шаблона непигового типа, который является известно во время компиляции:

template<bool index = true>
//                  ^^^^^^ note: the default value is now part of the template version
//                         see below to understand why
void writeVector(const vector<int>& vec) {
    for (size_t i = 0; i < vec.size(); ++i) {
        if (index) { // compile-time constant: this test will always be eliminated
            cout << i << "\t"; // this will only be kept if "index" is true
        }
        cout << vec[i] << "\n";
    }
}

void writeVector(const vector<int>& vec, bool index)
//                                            ^^^^^ note: no more default value, otherwise
//                                            it would clash with the template overload
{
    if (index) // runtime decision
        writeVector<true>(vec);
        //          ^^^^ map it to a compile-time constant
    else
        writeVector<false>(vec);
}

Таким образом, мы получаем скомпилированный код, который эквивалентен вашему второму примеру кода (внешний if/внутренний for), но без дублирования кода. Теперь мы можем сделать версию шаблона writeVector настолько сложной, насколько мы хотим, всегда будет поддерживаться один кусок кода.

Обратите внимание, как перегружена версия шаблона (которая принимает константу времени компиляции в форме аргумента шаблона непигового типа) и версию без шаблона (которая принимает переменную времени выполнения как аргумент функции). Это позволяет выбрать наиболее подходящую версию в зависимости от ваших потребностей, имея в обоих случаях довольно похожий, легко запоминаемый синтаксис:

writeVector<true>(vec);   // you already know at compile-time which version you want
                          // no need to go through the non-template runtime dispatching

writeVector(vec, index);  // you don't know at compile-time what "index" will be
                          // so you have to use the non-template runtime dispatching

writeVector(vec);         // you can even use your previous syntax using a default argument
                          // it will call the template overload directly