With "hooking" I mean the ability to non-intrusively 开发者_JAVA技巧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?
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;
}
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.
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++.
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.
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.
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.
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.
精彩评论