在for循环中避免if语句?

时间:2022-11-11 19:37:52

I have a class called Writer that has a function writeVector like so:

我有一个类叫做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";
    }
}

I'm trying not to have a duplicate code, while still worrying about the performance. In the function, I'm doing the if (index) check on every round of my for-loop, even though the result is always the same. This is against "worrying about the performance".

我尽量不让代码重复,但仍然担心性能。在函数中,我对for循环的每一轮执行if (index)检查,尽管结果总是相同的。这是反对“担心表现”。

I could easily avoid this by placing the check outside of my for-loop. However, I'll get loads of duplicate code:

通过将支票放在for循环之外,我可以很容易地避免这种情况。但是,我将会得到很多重复的代码:

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

So these are both "bad" solutions for me. What I've been thinking, is two private functions, one of them outs the index and then calls the other. The other one only outs the value. However, I can't figure out how to use it with my program, I'd still need the if check to see which one to call...

所以这对我来说都是“坏”的解决方案。我一直在想的是,两个私有函数,其中一个输出了索引,然后调用另一个。另一个只输出值。但是,我不知道如何在我的程序中使用它,我仍然需要if检查,看看应该调用哪个……

According to the problem, polymorphism seems like a correct solution. But I can't see how should I use it here. What would be the preferred way to solve this kind of problem?

根据这个问题,多态性似乎是一个正确的解决方案。但是我不知道该怎么用。解决这类问题的首选方法是什么?

This is not a real program, I'm just interested in learning how this kind of problem should be solved.

这不是一个真正的程序,我只是想学习如何解决这类问题。

3 个解决方案

#1


76  

Pass in the body of the loop as a functor. It gets inlined at compile-time, no performance penalty.

作为函子传入循环的主体。它在编译时被内联,没有性能损失。

The idea of passing in what varies is ubiquitous in the C++ Standard Library. It is called the strategy pattern.

在c++标准库中,传递不同内容的想法是普遍存在的。它被称为战略模式。

If you are allowed to use C++11, you can do something like this:

如果允许您使用c++ 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'; });
}

This code is not perfect but you get the idea.

这段代码并不完美,但你能理解。

In old C++98 it looks like this:

在旧的c++ 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;
}

Again, the code is far from perfect but it gives you the idea.

同样,代码远非完美,但它给了您一个概念。

#2


37  

In the function, I'm doing the if (index) check on every round of my for-loop, even though the result is always the same. This is against "worrying about the performance".

在函数中,我对for循环的每一轮执行if (index)检查,尽管结果总是相同的。这是反对“担心表现”。

If this is indeed the case, the branch predictor will have no problem in predicting the (constant) result. As such, this will only cause a mild overhead for mispredictions in the first few iterations. It's nothing to worry about in terms of performance

如果确实如此,分支预测器在预测(常数)结果时不会有问题。因此,这只会在最初的几个迭代中对错误的预测造成轻微的开销。就性能而言,这没什么好担心的

In this case I advocate for keeping the test inside the loop for clarity.

在这种情况下,我主张将测试保持在循环中以保持清晰。

#3


35  

To expand on Ali's answer, which is perfectly correct but still duplicates some code (part of the loop body, this is unfortunately hardly avoidable when using the strategy pattern)...

要扩展Ali的答案,这是完全正确的,但仍然重复了一些代码(循环体的一部分,很不幸,在使用策略模式时,这是不可避免的)……

Granted in this particular case the code duplication is not much but there's a way to reduce it even more, which comes in handy if the function body is bigger than just a few instructions.

在这个特定的例子中,代码重复并不多,但是有一种方法可以减少它,如果函数体比几个指令更大,这就很有用了。

The key is to use the compiler's ability to perform constant folding / dead code elimination. We can do that by manually mapping the runtime value of index to a compile-time value (easy to do when there are only a limited number of cases -- two in this case) and use a non-type template argument which is known at compile-time:

关键是要使用编译器执行持续折叠/死代码消除的能力。我们可以通过手工将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 * with the template overload
{
    if (index) // runtime decision
        writeVector<true>(vec);
        //          ^^^^ map it to a compile-time constant
    else
        writeVector<false>(vec);
}

This way we end up with compiled code which is equivalent to your second code example (outer if / inner for) but without duplicating the code ourselves. Now we can make the template version of writeVector as complicated as we want, there will always be a single piece of code to maintain.

这样,我们最终得到的编译代码就相当于您的第二个代码示例(外部if / inner),而不需要自己复制代码。现在我们可以将writeVector的模板版本变得非常复杂,始终有一段代码可以维护。

Note how the template version (which takes a compile-time constant in the form of a non-type template argument) and the non-template version (which takes a runtime variable as a function argument) are overloaded. This allows you to choose the most relevant version depending on your needs, having a rather similar, easy to remember syntax in both cases:

注意模板版本(以非类型模板参数的形式接受编译时常量)和非模板版本(以运行时变量作为函数参数)是如何重载的。这使您可以根据自己的需要选择最相关的版本,在这两种情况下都具有相当相似的、易于记忆的语法:

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

#1


76  

Pass in the body of the loop as a functor. It gets inlined at compile-time, no performance penalty.

作为函子传入循环的主体。它在编译时被内联,没有性能损失。

The idea of passing in what varies is ubiquitous in the C++ Standard Library. It is called the strategy pattern.

在c++标准库中,传递不同内容的想法是普遍存在的。它被称为战略模式。

If you are allowed to use C++11, you can do something like this:

如果允许您使用c++ 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'; });
}

This code is not perfect but you get the idea.

这段代码并不完美,但你能理解。

In old C++98 it looks like this:

在旧的c++ 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;
}

Again, the code is far from perfect but it gives you the idea.

同样,代码远非完美,但它给了您一个概念。

#2


37  

In the function, I'm doing the if (index) check on every round of my for-loop, even though the result is always the same. This is against "worrying about the performance".

在函数中,我对for循环的每一轮执行if (index)检查,尽管结果总是相同的。这是反对“担心表现”。

If this is indeed the case, the branch predictor will have no problem in predicting the (constant) result. As such, this will only cause a mild overhead for mispredictions in the first few iterations. It's nothing to worry about in terms of performance

如果确实如此,分支预测器在预测(常数)结果时不会有问题。因此,这只会在最初的几个迭代中对错误的预测造成轻微的开销。就性能而言,这没什么好担心的

In this case I advocate for keeping the test inside the loop for clarity.

在这种情况下,我主张将测试保持在循环中以保持清晰。

#3


35  

To expand on Ali's answer, which is perfectly correct but still duplicates some code (part of the loop body, this is unfortunately hardly avoidable when using the strategy pattern)...

要扩展Ali的答案,这是完全正确的,但仍然重复了一些代码(循环体的一部分,很不幸,在使用策略模式时,这是不可避免的)……

Granted in this particular case the code duplication is not much but there's a way to reduce it even more, which comes in handy if the function body is bigger than just a few instructions.

在这个特定的例子中,代码重复并不多,但是有一种方法可以减少它,如果函数体比几个指令更大,这就很有用了。

The key is to use the compiler's ability to perform constant folding / dead code elimination. We can do that by manually mapping the runtime value of index to a compile-time value (easy to do when there are only a limited number of cases -- two in this case) and use a non-type template argument which is known at compile-time:

关键是要使用编译器执行持续折叠/死代码消除的能力。我们可以通过手工将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 * with the template overload
{
    if (index) // runtime decision
        writeVector<true>(vec);
        //          ^^^^ map it to a compile-time constant
    else
        writeVector<false>(vec);
}

This way we end up with compiled code which is equivalent to your second code example (outer if / inner for) but without duplicating the code ourselves. Now we can make the template version of writeVector as complicated as we want, there will always be a single piece of code to maintain.

这样,我们最终得到的编译代码就相当于您的第二个代码示例(外部if / inner),而不需要自己复制代码。现在我们可以将writeVector的模板版本变得非常复杂,始终有一段代码可以维护。

Note how the template version (which takes a compile-time constant in the form of a non-type template argument) and the non-template version (which takes a runtime variable as a function argument) are overloaded. This allows you to choose the most relevant version depending on your needs, having a rather similar, easy to remember syntax in both cases:

注意模板版本(以非类型模板参数的形式接受编译时常量)和非模板版本(以运行时变量作为函数参数)是如何重载的。这使您可以根据自己的需要选择最相关的版本,在这两种情况下都具有相当相似的、易于记忆的语法:

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