Reputation: 35485
With "hooking" I mean the ability to non-intrusively override the behavior of a function. Some examples:
I have seen different implementations in various programming languages and libraries:
method_missing
%exception
keyword which is meant to wrap all functions in a try/catch block can be (ab)used for the purpose of hookingMy questions are:
Upvotes: 35
Views: 12366
Reputation: 21946
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.
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?
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.
Upvotes: 0
Reputation: 21113
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;
}
Upvotes: 13
Reputation: 101201
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.
Upvotes: 1
Reputation: 3481
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.
Upvotes: 5
Reputation: 11473
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.
Upvotes: 1
Reputation: 96266
To answer your first question:
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. There are a couple of techniques, here's a related question:
Emulating CLOS :before, :after, and :around in C++.
Upvotes: 4
Reputation: 45224
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.
Upvotes: 2