Подтвердить что ты не робот

Когда использовать функцию std:: вместо наследования?

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

struct Function
{
    virtual int operator()( int ) const =0;
};
struct A
    : public Function
{
    int operator()( int x ) const override { return x; }
};

Используя std::function, мы можем переписать это как

using Function = std::function<int (int)>;
struct A
{
    int operator()( int x ) const { return x; }
};

Чтобы сделать более понятным, как связаны два фрагмента: оба они могут использоваться следующим образом:

int anotherFunction( Function const& f, int x ) { return f( x ) + f( x ); }

int main( int argc, char **argv )
{
    A f;
    anotherFunction( f, 5 );
    return 0;
}

Последний подход более гибкий, потому что нам не нужно выводить наши классы из некоторого общего базового класса. Единственная связь между Function -подобными объектами основана на их возможностях. Что касается объектно-ориентированного программирования, то его можно считать менее чистым (но не в плане функционального программирования, конечно).

Кроме того, существуют ли другие различия между этими двумя решениями? Существуют ли какие-либо общие рекомендации, когда использовать какое из решений или это только вопрос личных предпочтений? Есть ли случаи, когда одно решение выходит из другого?

4b9b3361

Ответ 1

Небольшая поправка: обратите внимание, что это:

int anotherFunction( Function f, int x ) { return f( x ) + f( x ); }

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

Скорее всего, вам понадобится взять ваш Function -положенный объект по ссылке (возможно, по ссылке const), чтобы воспользоваться полиморфизмом:

int anotherFunction( Function const& f, int x ) { return f( x ) + f( x ); }

И это не очень функционально, поэтому, если вы увлекаетесь функциональным программированием (как вам кажется), вы можете избежать его только из-за этого.


Тем не менее, вот что я хотел бы дать:

  • Если вы можете, используйте шаблоны:

    template<typename F>
    int anotherFunction(F f, int x) { return f(x) + f(x); }
    

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

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

      anotherFunction([] (int x) { return x * 2; }, 42);
      

      Также как:

      anotherFunction(myFxn, 42); // myFxn is a regular function
      

      Или даже:

      anotherFunction(my_functor(), 42); // my_functor is a class
      
    • Превосходная производительность: поскольку вы не вызываете виртуальную таблицу, а компилятор знает, как вызовы функций будут разрешены, он может включить вызов, чтобы дать вам большую производительность (если он сочтет это разумным).

  • Если вы не можете использовать шаблоны, потому что функция, которая будет вызываться, будет определена во время выполнения, использовать std::function:

     int anotherFunction(std::function<int (int)> f, int x) 
     { 
         return f(x) + f(x); 
     }
    

    Это также даст вам достаточную гибкость для передачи в lambdas, указателях функций, функторов, в основном любого вызываемого объекта. См., Например, fooobar.com/info/43185/....

    Использование std::function может привести к значительным непродолжительным накладным расходам в отношении дизайна на основе шаблонов и, возможно, также незначительных незначительных накладных расходов в отношении жестко закодированного решения наследования, подобного тому, которое вы начертите, но оно дает вам гибкость и стандартная идиома. Более того, как всегда, когда производительность вызывает беспокойство, делайте измерения для подтверждения любого предположения - вы можете получить неожиданные результаты.

    Обычно вам нужно прибегнуть к подобному дизайну на основе std::function, если вы хотите хранить вызываемые объекты любого типа для последующего вызова, как в случае Конструкция шаблона команды или когда у вас есть гетерогенная коллекция вызываемых объектов, которые нужно обрабатывать и вызывать в общем случае. Для обсуждения вопроса о том, когда использовать std::function вместо шаблонов, см. fooobar.com/info/43185/....

  • Итак, когда вы должны прибегать к жесткому кодированию с использованием наследования? Ну, во всех тех случаях, когда 1. и 2. не жизнеспособны - честно говоря, я не могу сейчас придумать, но я уверен, что кто-то может придумать угловой случай. Обратите внимание, что С++ 11 не требуется для использования std::function -подобной идиомы, поскольку Boost имеет реализацию boost::function и boost::bind, которая была датирована (и послужила источником вдохновения для) С++ 11 std::function и std::bind.


TL; DR: используйте шаблоны или std::function.