35

With "hooking" I mean the ability to non-intrusively override the behavior of a function. Some examples:

  • Print a log message before and/or after the function body.
  • Wrap the function body in a try catch body.
  • Measure duration of a function
  • etc...

I have seen different implementations in various programming languages and libraries:

  • Aspect Oriented Programming
  • JavaScript's first class functions
  • OOP decorator pattern
  • WinAPI subclassing
  • Ruby's method_missing
  • SWIG's %exception keyword which is meant to wrap all functions in a try/catch block can be (ab)used for the purpose of hooking

My questions are:

  • IMO this is such an incredibly useful feature that I wonder why it has never been implemented as a C++ language feature. Are there any reasons that prevent this from being made possible?
  • What are some recommended techniques or libraries to implement this in a C++ program?
StackedCrooked
  • 34,653
  • 44
  • 154
  • 278
  • 6
    Raymond Chen recently [wrote](http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx) about how Windows accomplishes this – Praetorian Oct 12 '11 at 17:06
  • @R.MartinhoFernandes Yep, forgot to mention the most relevant one. – StackedCrooked Oct 12 '11 at 17:08
  • 22
    What the hell is "non-constructive" about _What are some recommended techniques or libraries to implement this in a C++ program?_ – sbi Oct 12 '11 at 17:20
  • 6
    This is a valid question. Please do not vote to close it. – John Dibling Oct 12 '11 at 17:32
  • 1
    `IMO this is an incredibly useful feature, so why is it not a C++ language feature?` seemed like non-constructive language bashing to me. – Mark B Oct 12 '11 at 17:39
  • The 3 examples can probably be done with RAII and templates. It might be messy to set up, bug I imagine it could work. – Pubby Oct 12 '11 at 17:43
  • 4
    @MarkB I didn't expect that this would be perceived as language bashing. Reworded it a bit. C++ is my favorite programming language btw. – StackedCrooked Oct 12 '11 at 17:47
  • MAybe it belongs on programmers instead of stackoverflow? – Matt K Oct 12 '11 at 17:47
  • 1
    @StackedCrooked: it wasn't bashing, but it can be perceived as bashing by C++-obsessed zealots. Not sure if saying that C++ is your favorite language is enough... try adding that template metaprogramming is the best thing in the world and may be it will be ok :-) – 6502 Oct 12 '11 at 18:11
  • 1
    @6502 I'm a placater. I feel dirty now. – StackedCrooked Oct 12 '11 at 18:15
  • 8
    @6502: You're obviously pretty out of touch. Template metaprogramming is so 2003. *lambda expressions* are now the greatest thing in the world. Get with it, man! – John Dibling Oct 12 '11 at 18:52
  • @StackedCrooked: Hey... it was just a joke :-) Honestly I think your question is quite interesting. Unfortunately C++ introspection and metaprogramming is way too weak to give an answer within the language... so either you rely on unportable non-C++ solutions or you've to accept some heavy compromise. – 6502 Oct 12 '11 at 19:15
  • 10
    I knew that, sooner or later, some, erm, _ill-advised soul_ with a legitimating 34 upvotes for answers in the C++ tag would come along and _unthinkingly_ cast the missing last close, despite the fact that this a wholly legitimate question, especially after it was changed to suit the critics! _Sigh._ ___Voted to reopen.___ – sbi Oct 13 '11 at 11:51
  • C++ and C just go for performance. Hence the lack of the bells and whistles. – Ed Heal Oct 12 '11 at 17:40
  • 1
    This seems like a perfectly valid question even in the first instance – Puppy Oct 13 '11 at 14:55
  • @DeadMG: Yet the mode declined to remove the close votes when I flagged for that. – sbi Oct 14 '11 at 18:07

7 Answers7

13

If you're talking about causing a new method to be called before/after a function body, without changing the function body, you can base it on this, which uses a custom shared_ptr deleter to trigger the after-body function. It cannot be used for try/catch, since the before and after need to be separate functions using this technique.

Also, the version below uses shared_ptr, but with C++11 you should be able to use unique_ptr to get the same effect without the cost of creating and destroying a shared pointer every time you use it.

#include <iostream>
#include <boost/chrono/chrono.hpp>
#include <boost/chrono/system_clocks.hpp>
#include <boost/shared_ptr.hpp>

template <typename T, typename Derived>
class base_wrapper
{
protected:
  typedef T wrapped_type;

  Derived* self() {
    return static_cast<Derived*>(this);
  }

  wrapped_type* p;

  struct suffix_wrapper
  {
    Derived* d;
    suffix_wrapper(Derived* d): d(d) {};
    void operator()(wrapped_type* p)
    {
      d->suffix(p);
    }
  };
public:
  explicit base_wrapper(wrapped_type* p) :  p(p) {};


  void prefix(wrapped_type* p) {
     // Default does nothing
  };

  void suffix(wrapped_type* p) {
     // Default does nothing
  }

  boost::shared_ptr<wrapped_type> operator->() 
  {
    self()->prefix(p);
    return boost::shared_ptr<wrapped_type>(p,suffix_wrapper(self()));
  }
};




template<typename T>
class timing_wrapper : public base_wrapper< T, timing_wrapper<T> >
{
  typedef  base_wrapper< T, timing_wrapper<T> > base;
  typedef boost::chrono::time_point<boost::chrono::system_clock, boost::chrono::duration<double> > time_point;

  time_point begin;
public:
  timing_wrapper(T* p): base(p) {}


  void prefix(T* p) 
  {
    begin = boost::chrono::system_clock::now();
  }

  void suffix(T* p)
  {
    time_point end = boost::chrono::system_clock::now();

    std::cout << "Time: " << (end-begin).count() << std::endl;
  }
};

template <typename T>
class logging_wrapper : public base_wrapper< T, logging_wrapper<T> >
{
  typedef  base_wrapper< T, logging_wrapper<T> > base;
public:
  logging_wrapper(T* p): base(p) {}

  void prefix(T* p) 
  {
    std::cout << "entering" << std::endl;
  }

  void suffix(T* p) 
  {
    std::cout << "exiting" << std::endl;
  }

};


template <template <typename> class wrapper, typename T> 
wrapper<T> make_wrapper(T* p) 
{
  return wrapper<T>(p);
}


class X 
{
public:
  void f()  const
  {
    sleep(1);
  }

  void g() const
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }

};



int main () {

  X x1;


  make_wrapper<timing_wrapper>(&x1)->f();

  make_wrapper<logging_wrapper>(&x1)->g();
  return 0;
}
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
Dave S
  • 20,507
  • 3
  • 48
  • 68
5

There are compiler-specific features you can leverage such as, such as GCC's -finstrument-functions. Other compilers will likely have similar features. See this SO question for additional details.

Another approach is to use something like Bjarne Stroustrup's function wrapping technique.

Community
  • 1
  • 1
Void - Othman
  • 3,441
  • 18
  • 18
4

To answer your first question:

  • Most dynamic languages have their method_missing constructs, PHP has a magic methods (__call and __callStatic) and Python has __getattr__. I think the reason this isn't available in C++ that it goes against the typed nature of C++. Implementing this on a class means that any typos will end up calling this function (at runtime!), which prevents catching these problems at compile time. Mixing C++ with duck typing doesn't seem to be a good idea.
  • C++ tries to be as fast as possible, so first class functions are out of question.
  • AOP. Now this is more interesting, techincally there's nothing that prevents this being added to the C++ standard (apart from the fact that adding another layer of complexity to an already extremly complex standard is might not be a good idea). In fact there are compilers which are able to wave code, AspectC++ is one of them. A year ago or so it wasn't stable but it looks like since then their managed to release 1.0 with a pretty decent test suite so it might does the job now.

There are a couple of techniques, here's a related question:

Emulating CLOS :before, :after, and :around in C++.

Community
  • 1
  • 1
Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
  • 1
    Thanks, AspectC++ seems to provide what I ask for. Requiring a custom compiler for this language extension is a bit scary though :) – StackedCrooked Oct 12 '11 at 17:56
2

IMO this is an incredibly useful feature, so why is it not a C++ language feature? Are there any reasons that prevent this from being made possible?

C++ the language does not provide any means to do so directly. However, it also does not pose any direct constraint against this (AFAIK). This type of feature is easier to implement in an interpreter than in native code, because the interpret is a piece of software, not a CPU streaming machine instructions. You could well provide a C++ interpreter with support for hooks if you wanted to.

The problem is why people use C++. A lot of people are using C++ because they want sheer execution speed. To achieve that goal, compilers output native code in the operating system's preferred format and try to hard code as much stuff into the compiled executable file. The last part often means computing addresses at compile/link time. If you fix a function's address at that time (or even worse, inline the function body) then there is no more support for hooks.

That being said, there are ways to make hooking cheap, but it requires compiler extensions and is totally not portable. Raymond Chen blogged about how hot patching is implemented in the Windows API. He also recommends against its use in regular code.

André Caron
  • 44,541
  • 12
  • 67
  • 125
1

This is not a C++ thing, but to accomplish some of things you mention, I have used the LD_PRELOAD environment variable in *nix systems. A good example of this technique in action is the faketime library that hooks into the time functions.

frankc
  • 11,290
  • 4
  • 32
  • 49
1

At least on c++ framework that I use provides a set of pure virtual classes

class RunManager;
class PhysicsManager;
// ...

Each of which defined a set of actions

void PreRunAction();
void RunStartAction()
void RunStopAction();
void PostRunAction();

which are NOPs, but which the user can override where deriving from the Parent class.

Combine that with conditional compilation (yeah, I know "Yuk!") and you can get what you want.

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
0
  1. There has to be a way to implement the functionality without affecting the performance of code that doesn't use the functionality. C++ is designed on the principle that you only pay performance costs for the features you use. Inserting if checks in every function to check if its been overridden would be unacceptably slow for many C++ projects. In particular, making it work so that there's no performance cost while still allowing for independent compilation of the overridden and overriding functions will be tricky. If you only allow for compile time overriding, then it's easier to do performantly (the linker can take care of overwriting addresses), but you're comparing to ruby and javascript which let you change these things at runtime.

  2. Because it would subvert the type system. What does it mean for a function to be private or non-virtual if someone can override its behavior anyway?

  3. Readability would greatly suffer. Any function might have its behavior overridden somewhere else in the code! The more context you need to understand what a function does, the harder it is to figure out a large code base. Hooking is a bug, not a feature. At least if being able to read what you wrote months later is a requirement.

Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • 1
    *Hooking is a bug, not a feature* - AOP disagrees with you, as do tools such as profilers with non-instrusive instrumentation. – Björn Pollex Oct 13 '11 at 11:56
  • The existence of the bug doesn't refute that the bug is a bug ;) So I don't find the existence of AOP particularly convincing. Profilers don't insert arbitrary code changing the behavior of the function. If someone came up with an AOP type system that could prove that the overriding wouldn't create side effects that violate assumptions by users of the code, then I could get on board with it. – Joseph Garvin Oct 13 '11 at 15:04
  • I agree with (1) and (2), but re (3), I don't see that hooking is any more of a bug than having open namespaces and allowing overriding. They both have that action-at-a-distance quality. Of course you could argue that they're both bugs :) – j_random_hacker Oct 24 '11 at 10:52
  • @j_random_hacker: I'm inclined to say they both are ;) – Joseph Garvin Oct 25 '11 at 15:39