1

In our code base, we have a lot of classes like so:

class DirectoryCallback {
public: 
    virtual ~DirectoryCallback(){};
    virtual void process(const std::string &path) = 0;
};

They typically have just one purpose: wrap a function call. Extending these interfaces is boresome. Boredom doesn't harm, but I was wondering: what use is this pattern since we have lambdas and std::function in C++? Because that's exactly what a they can do... wrap a function call.

xtofl
  • 40,723
  • 12
  • 105
  • 192
  • Should these callback interfaces die? Yep. Are they actually dead? Look around you and tell me. – n. m. could be an AI Mar 20 '15 at 07:39
  • 2
    Typically you may can't to store these callback objects somewhere and call them later. This can't really be done with lambdas. But I would say lambdas + `std::function` types make the use of these callback classes rather obsolete (people might still argue about type safety etc. i.e. a callback base class might enforce some invariants e.g. via the template method) – juanchopanza Mar 20 '15 at 07:40
  • `s/you may can't/you my want/` in my previous comment – juanchopanza Mar 20 '15 at 07:56
  • @juanchopanza: that's an answer to my question. Indeed I forgot to add the `std::function` in the story. – xtofl Mar 20 '15 at 08:04
  • Would it be useful if I posted the comment as an answer? – juanchopanza Mar 20 '15 at 08:10

1 Answers1

0

Bit of a mess this answer, but a few things to think about....

A virtual function - used as a callback - is more inherently restrictive:

  • you know a derived classes must specify an implementation (given your = 0), but it won't vary during the derived object's lifetime (something similar may be done with const std::function objects set in the initialiser list, but need not be),

  • you know the callbacks are active from the time the constructor completes until destruction

By way of contrast, someone coming to grips with code that specifies a set of callback lambdas has to look more carefully at how and when they're set, and whether they may be varied during the lifetime of the object. Lambdas may be capturing more inputs and context from the function they're created in - that adds to the potential complexity.

Setting std::function callbacks may require more explicit procedural code to set the std::function variables, unless that's done exclusively in the constructor's initialisation list. virtual function overrides are more declarative, which some people prefer.

The most-derived implementation of a virtual function is used, while specifying a lambda to a std::function in the most-derived constructor may require explicitly passing it to base class constructors at all levels through the hierarchy.

The const-ness of virtual functions works intuitively, but derived classes specifying behaviour by setting std::function members to lambdas have opportunity to grab non-const access to the derived object, regardless of what the base class design suggests those callbacks should need.

virtual dispatch is understood intimately by the compiler, and often resolved - with the function potentially inlined - when the dynamic type of the object is known at compile time. It's a stretch to think equivalent optimisation would necessarily be achieved for std::function objects where the lambdas are nominally set by derived functions at runtime....

Virtual functions are efficiently factored as per-object pointers to per-class tables, which would be more trouble to orchestrate manually using std::functions. The easy approach of a std::function member per callback is less memory efficient.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252