13

I've just learned about what std::function really is about and what it is used for and I have a question: now that we essentially have delegates, where and when should we use Abstract Base Classes and when, instead, we should implement polymorphism via std::function objects fed to a generic class? Did ABC receive a fatal blow in C++11?

Personally my experience so far is that switching delegates is much simpler to code than creating multiple inherited classes each for particular behaviour... so I am a little confused abotu how useful Abstract Bases will be from now on.

Doug T.
  • 64,223
  • 27
  • 138
  • 202
Zeks
  • 2,265
  • 20
  • 32
  • 2
    How is `std::function` any different from function-pointers (*in this context* which )? I don't see any difference. If so, then what it has to with C++11? You could use function-pointers to implement runtime polymorphism in pre-C++11 as well. – Nawaz Mar 07 '12 at 17:31
  • I am not even sure how to start answering ... for example std::function can contain pointer to functions with different type and number of arguments because you can std::bind them prior to actually passing to it. And, obviously , it is way simplier to use. If anything you could ask about what can it do what _functors_ can not... this question I would understand. Still, addition of standard and *simple* way to create delegates is what spawned my question anyway. – Zeks Mar 07 '12 at 17:35
  • "*Still, addition of standard and SIMPLE way to create delegates is what spawned my question anyway.*"... But it is *not simpler* than using abstract base and virtual functions, then what is the point? – Nawaz Mar 07 '12 at 17:37
  • It _is_ simplier.From my experience - _much_ simplier. And it keeps hierarchy clean. – Zeks Mar 07 '12 at 17:39
  • Show me that by writing a sample code, so I will believe you. – Nawaz Mar 07 '12 at 17:41
  • Sorry... having toruble with posting this :) wait a bit – Zeks Mar 07 '12 at 17:43
  • What is ABC polymorphism? Also, why don't you post a sample code to illustrate (and prove) what you said? – Nawaz Mar 07 '12 at 17:44
  • Just think about it. To use ABC you require to: A: Write an abstract base. B: Write multiple Derivatives each for its own job. Essentially you are writing boilerplate for each of derivatives. If you use std::function it is as simple as A: write an interface class containing an std::function for each of its actions B: write different functions you will use separately In this case your class hierarchy is just 1 class and a bunch of functions that you can assign at runtime. You can even change class behaviour _partially_. For example using functions a,b and c for its actions, or a, d and e. – Zeks Mar 07 '12 at 17:48
  • 3
    @Nawaz: The key is probably that `std::function` does type erasure, whereas functors alone don't. This means that a `std::function` data member can encapsulate an injected dependency, in the same way as a pointer-to-abstract-base-class. Hence the possibility of using it as a replacement without major design changes. To do the same thing with a functor, you need to template the class into which the dependency is injected. If you don't know what dynamic polymorphism is for, then tbh you can't really have an opinion whether abstract base classes are or are not (still) the best way to achieve it. – Steve Jessop Mar 07 '12 at 18:01
  • @SteveJessop, thx, I am not so good with the technical language to explain it this way myself :) – Zeks Mar 07 '12 at 18:12

2 Answers2

19

Prefer well defined interfaces over callbacks

The problem with std::function (previously boost::function) is that most of the time you need to have a callback to a class method, and therefore need to bind this to the function object. However in the calling code, you have no way to know if this is still around. In fact, you have no idea that there even is a this because bind has molded the signature of the calling function into what the caller requires.

This can naturally cause weird crashes as the callback attempts to fire into methods for classes that no longer exist.

You can, of course use shared_from_this and bind a shared_ptr to a callback, but then your instance may never go away. The person that has a callback to you now participates in your ownership without them even knowing about it. You probably want more predictable ownership and destruction.

Another problem, even if you can get the callback to work fine, is with callbacks, the code can be too decoupled. The relationships between objects can be so difficult to ascertain that the code readability becomes decreased. Interfaces, however, provide a good compromise between an appropriate level of decoupling with a clearly specified relationship as defined be the interface's contract. You can also more clearly specify, in this relationship, issues like who owns whom, destrcution order, etc.

An additional problem with std::function is that many debuggers do not support them well. In VS2008 and boost functions, you have to step through about 7 layers to get to your function. Even if all other things being equal a callback was the best choice, the sheer annoyance and time wasted accidentally stepping over the target of std::function is reason enough to avoid it. Inheritance is a core feature of the language, and stepping into an overridden method of an interface is instantaneous.

Lastly I'll just add we don't have delegates in C++. Delegates in C# are a core part of the language, just like inheritance is in C++ and C#. We have a std library feature which IMO is one layer removed from a core language functionality. So its not going to be as tightly integrated with other core features of the language. It instead helps formalize the idea of function objects that have been a C++ idiom for a good while now.

Doug T.
  • 64,223
  • 27
  • 138
  • 202
  • Thx, the point about "this" is important, I have not considered that. I will take this in mind. – Zeks Mar 07 '12 at 17:53
  • @Zeks yup. I used to be `boost::function` happy but then I later regretted it. Its a feature that has specific uses but IMO shouldn't be the default answer to problems. – Doug T. Mar 07 '12 at 17:55
  • Btw, is there any article about possible pitfalls when using std/boost::function ? Is there more to it than having to worry about "this" ? – Zeks Mar 07 '12 at 18:28
  • @Zeks well I don't know of any articles, I'm speaking from experience. the "this" problem is a big annoying one but it speaks to a larger problem in that your creating this psuedo-relationship due to the type-erasure features of `std::function`. The caller is either pointing to something that may go away or participating in owreship unknowingly. This speaks to the larger problem in that with boost::function callbacks you haven't spelled out how one objects lifetime relates to anothers. You haven't cleanly delineated responsibility between two classes. – Doug T. Mar 07 '12 at 20:11
  • Oh, but I am not using this relationship between 2 classes. I have only 1 class that basically receives pointer to a free function that can change at runtime. The calss is "generic" as in - it has a lot of placeholders for switchable free functions. The thing I like there is that with the help of std::bind those functions must not necessarily have the same amount of parameters. Which, I thought, makes this approach rather more flexible than deriving from Abstract. (thus - the original question :)) – Zeks Mar 07 '12 at 20:19
  • Is this kind of approach safe? I can see that it still triggers your point of "too much decoupling" and debugger problems though :) – Zeks Mar 07 '12 at 20:27
  • I think I will need to illustrate what I mean. Will do this tomorrow:) – Zeks Mar 07 '12 at 20:34
1

I do not see how one can come to the conclusion that function pointers make abstract base classes obsolete.

A class, encapsulates methods and data that pertains to it.

A function pointer, is a function pointer. It has no notion of an encapsulating object, it merely knows about the parameters passed to it.

When we write classes, we are describing well-defined objects.

Function pointers are great for a good number of things, but changing the behaviour of an object isn't necessarily the target-audience (though I admit there may be times when you would want to do so, e.g. callbacks, as Doug.T mentions).

Please don't confuse the two.

Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
  • I am not saying they are obsolette. Just that std::function can be applied to perform somewhat similar job. I am asking this _because_ I am not sure where are the boundaries between the two :) – Zeks Mar 07 '12 at 17:56