Когда использовать std :: function вместо наследования?

В некоторых случаях std::function может заменить наследство. Следующие два фрагмента кода очень похожи (примерно одинаковые затраты при вызове функции, почти одинаковое использование в сигнатурах и в большинстве случаев std :: function не требует от нас создания дополнительной копии 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подобные объекты основаны на их возможностях. С точки зрения объектно-ориентированного программирования его можно считать менее чистым (но не с точки зрения функционального программирования, конечно).

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

11

Решение

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

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 ); }

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


Тем не менее, вот руководство, которое я бы дал:

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

    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
      
    • Превосходную производительность: поскольку вы не вызываете виртуальную таблицу и компилятор знает, как будут разрешаться вызовы функций, он может встроить вызов, чтобы повысить производительность (если он сочтет это разумным).

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

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

    Это также даст вам достаточно гибкости, чтобы передавать лямбды, указатели функций, функторы, в основном любой вызываемый объект. Смотрите, например, этот вопрос&А на StackOverflow.

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

    Вам обычно нужно прибегнуть к такого рода std::functionдизайн, когда вы хотите хранить вызываемые объекты любого типа для последующего вызова, как в случае Шаблон проектирования команд, или когда у вас есть разнородная коллекция вызываемых объектов для обработки и общего вызова. Для обсуждения того, когда использовать std::function вместо шаблонов смотрите этот вопрос&А на StackOverflow.

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


TL; DR: Использовать шаблоны или std::function,

19

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector