Ответ 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
, которая была датирована (и послужила источником вдохновения для) С++ 11std::function
иstd::bind
.
TL; DR: используйте шаблоны или std::function
.