Наиболее эффективный метод выполнения функций в неизвестном порядке - PullRequest
2 голосов
/ 06 сентября 2011

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

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

До сих пор я ориентировочно использовал этот способ, который мог бы лучше объяснить, какова моя цель:

class Behavior{
    public:
    virtual void act(Object * obj) = 0;
};

class SpecificBehavior : public Behavior{
    // many classes like this exist
    public:
    void act(Object * obj){ /* do something specific with obj*/ };
};

class Object{
    public:
    std::list<Behavior*> behavior;

    void behave(){
        std::list<Behavior*>::iterator iter = behavior.front();
        while(iter != behavior.end()){
             iter->act(this);
             ++iter;
        };
    };
};

Мой вопрос - что наиболее эффективно?способ в C ++ организовать такой пул функций, с точки зрения производительности и ремонтопригодности.Это для некоторых исследований ИИ, которые я делаю, и эта методология - то, что наиболее близко соответствует тому, чего я пытаюсь достичь.

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

Ответы [ 4 ]

5 голосов
/ 06 сентября 2011

Если функции поведения не имеют состояния и принимают только один аргумент Object, я бы пошел с контейнером объектов функций:

#include <functional>
#include <vector>

typedef std::function<void(Object &)> BehaveFun;
typedef std::vector<BehaveFun> BehaviourCollection;

class Object {
  BehaviourCollection b;

  void behave() {
    for (auto it = b.cbegin(); it != b.cend(); ++it) *it(*this);
  }
};

Теперь вам просто нужно загрузить все свои функции вколлекция.

2 голосов
/ 06 сентября 2011

Я бы сохранил это точно так же, как у вас. Производительность должна быть в порядке (может быть дополнительное перенаправление из-за поиска vtable, но это не должно иметь значения.)

Мои причины сохранить это как есть:

  1. Вы можете поднять общее подчиненное поведение в промежуточный класс между поведением и вашими классами реализации. Это не так просто, используя указатели функций.

    struct AlsoWaveArmsBase : public Behaviour 
    {
       void act( Object * obj )
       {
          start_waving_arms(obj); // Concrete call
          do_other_action(obj);   // Abstract call
          end_waving_arms(obj);   // Concrete call
       }
       void start_waving_arms(Object*obj);
       void end_waving_arms(Object*obj);
       virtual void do_other_actions(Object * obj)=0;
    };
    
    struct WaveAndWalk : public AlsoWaveArmsBase
    {
       void do_other_actions(Object * obj) { walk(obj); }
    };
    
    struct WaveAndDance : pubic AlsoWaveArmsBase
    {
       void do_other_actions(Object * obj) { walk(obj); }    
    }
    
  2. Возможно, вы захотите использовать состояние в вашем поведении

    struct Count : public Behavior
    {
       Behaviour() : i(0) {}
       int i;
       void act(Object * obj)
       {
          count(obj,i);
          ++i;
       }
    }
    
  3. Возможно, вы захотите добавить вспомогательные функции, например, Вы можете добавить can_act, например:

    void Object::behave(){
      std::list<Behavior*>::iterator iter = behavior.front();
      while(iter != behavior.end()){
         if( iter->can_act(this) ){
            iter->act(this);
         }
         ++iter;
       };
     };
    

ИМО, эти гибкие возможности перевешивают преимущества перехода к чисто функциональному подходу.

2 голосов
/ 06 сентября 2011

Если основное, что вы будете делать с этой коллекцией, это итерации по ней, вы, вероятно, захотите использовать vector, так как разыменование и приращение ваших итераторов будут равносильны простой арифметике указателей.

Если вы хотите использовать все свои ядра, а ваши операции не делятся между собой, вы можете взглянуть на такую ​​библиотеку, как Intel TBB (см. parallel_for * )

1 голос
/ 06 сентября 2011

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

Я бы не использовал std :: function, потому что он не очень производительный (хотя это может отличаться в разных реализациях).См. это и это .Указатели на функции работают так же быстро, как и тогда, когда вам нужен такой динамизм во время выполнения.

Если вам нужно улучшить производительность, я предлагаю рассмотреть улучшение алгоритма, а не его реализацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...