Итерации по вектору и вызов функций - PullRequest
4 голосов
/ 19 января 2009

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

class Small
{
  public:
    void foo(); 
    void bar(int x);
    // and many more functions
};

class Big
{
  public:
    void foo()
    {
        for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
            VectorOfSmalls[i]->foo();
    }
    void bar(int x)
    {
        for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
            VectorOfSmalls[i]->bar(x);
    }
    // and many more functions
  private:
    vector<Small*> VectorOfSmalls;
};

Я хочу упростить код и найти способ не дублировать другой вектор в каждой функции.

Я рассмотрел создание функции, которая получает указатель на функцию и вызывает указанную функцию для каждого члена вектора. Но я не уверен, что использование указателей на функции в C ++ - хорошая идея.

Я также думал о функторах и функционоидах , но это заставит меня создать класс для каждой функции, и это звучит как излишнее убийство.

Другим возможным решением является создание функции, которая получает строку и вызывает команду в соответствии со строкой:

void Big::call_command(const string & command)
{
    for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
    {
       if (command == "foo")
           VectorOfSmalls[i]->foo();
       else if (command == "bar")
           VectorOfSmalls[i]->bar();
    }
}
void Big::foo()
{
    call_command("foo");
}

Но это может работать медленно (ненужное создание строки вместо просто вызова функции), а также создает проблему, если функции имеют другую сигнатуру.

Так что бы вы порекомендовали? Должен ли я оставить все так же, как сейчас?

РЕДАКТИРОВАТЬ: я могу использовать только STL и не Boost (старые компиляторы).

Ответы [ 3 ]

16 голосов
/ 19 января 2009

Ну, вы можете переписать циклы for, чтобы использовать итераторы и другие STL, например:

void foo() {
    std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::foo));
}

void bar() {
    std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::bar));
}

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

Если вы используете одну функцию, принимающую параметр, чтобы решить, что делать, я бы использовал перечисление и переключатель, подобные этому, это было бы более эффективно, чем строки и каскад, если. Кроме того, в вашем примере у вас есть if, чтобы решить, что делать внутри цикла. Более эффективно выполнять проверку вне цикла и иметь избыточные копии цикла, поскольку «какую команду» нужно решать только один раз за вызов. (ПРИМЕЧАНИЕ: вы можете сделать команду параметром шаблона, если он известен во время компиляции, что звучит так, как оно есть).

class Big {
public:
    enum Command {
        DO_FOO,
        DO_BAR
    };

void doit(Command cmd) {
    switch(cmd) {
    case DO_FOO:
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::foo));
        break;
    case DO_BAR:
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(&Small::bar));
        break;
    }
};

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

class Big {
public:
    template<void (Small::*fn)()>
    void doit() {
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(fn));
    }
};

Тогда вы можете сделать:

Big b;
b.doit<&Small::foo>();
b.doit<&Small::bar>();

Хорошая вещь и в этом, и в методах обычных параметров заключается в том, что Big не нужно изменять, если вы измените small, чтобы иметь больше подпрограмм! Я думаю, что это предпочтительный метод.

Если вы хотите иметь возможность обрабатывать один параметр, вам также нужно добавить bind2nd, вот полный пример:

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>

class Small {
public:
    void foo() { std::cout << "foo" << std::endl; }
    void bar(int x) { std::cout << "bar" << std::endl; }
};


class Big {
public:
    template<void (Small::*fn)()>
    void doit() {
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::mem_fun(fn));
    }

    template<class T, void (Small::*fn)(T)>
    void doit(T x) {
        std::for_each(VectorOfSmalls.begin(), VectorOfSmalls.end(), std::bind2nd(std::mem_fun(fn), x));
    }
public:
    std::vector<Small *> VectorOfSmalls;
};

int main() {
    Big b;
    b.VectorOfSmalls.push_back(new Small);
    b.VectorOfSmalls.push_back(new Small);

    b.doit<&Small::foo>();
    b.doit<int, &Small::bar>(5);
}
4 голосов
/ 19 января 2009

Если вы используете библиотеку std, вы должны взглянуть на for_each .

Вы упомянули, что использование указателей на функции в C ++ может быть не очень хорошей идеей, но, если учесть, что беспокойство - это скорость, вы должны убедиться, что это даже узкое место производительности, в котором вы находитесь, прежде чем беспокоиться.

0 голосов
/ 19 января 2009

Попробуйте boost :: function и boost :: bind :

void Big::call_command(const boost::function<void (Small*)>& f)
{
    for (size_t i = 0; i <  VectorOfSmalls.size(); i++)
    {
        f(VectorOfSmalls[i]);
    }
}

int main()
{
    Big b;
    b.call_command(boost::bind(&Small::foo, _1));
    b.call_command(boost::bind(&Small::bar, _1, 5));
}
...