“挂钩”是指非侵入性地覆盖函数行为的能力。一些例子:
我在各种编程语言和库中看到了不同的实现:
method_missing
%exception
关键字用于包装try / catch块中的所有函数,可以(ab)用于挂钩我的问题是:
答案 0 :(得分:13)
如果您正在讨论在函数体之前/之后调用新方法而不更改函数体,则可以将其基于this,它使用自定义shared_ptr
删除器触发后身功能。它不能用于try/catch
,因为之前和之后需要使用这种技术的单独函数。
此外,下面的版本使用shared_ptr
,但使用C ++ 11,您应该能够使用unique_ptr
获得相同的效果,而无需每次创建和销毁共享指针的成本用它。
#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;
}
答案 1 :(得分:5)
您可以利用特定于编译器的功能,例如GCC的-finstrument-functions。其他编译器可能具有类似的功能。有关其他详细信息,请参阅此SO question。
另一种方法是使用类似Bjarne Stroustrup's function wrapping技术的东西。
答案 2 :(得分:4)
回答你的第一个问题:
method_missing
个结构,PHP有magic methods(__call
和__callStatic
),Python有__getattr__
。我认为这在C ++中不可用,它违背了C ++的类型性质。在类上实现它意味着任何拼写错误最终都会调用此函数(在运行时!),这可以防止在编译时捕获这些问题。将C ++与鸭子打字混合似乎不是一个好主意。 有几种技巧,这是一个相关的问题:
答案 3 :(得分:2)
IMO这是一个非常有用的功能,为什么它不是C ++语言功能?是否有任何理由阻止这种情况发生?
C ++语言没有提供直接这样做的任何方法。但是,它也没有对此(AFAIK)构成任何直接约束。这种类型的功能在解释器中比在本机代码中更容易实现,因为解释是一个软件,而不是CPU流机器指令。如果你愿意,你可以提供一个支持钩子的C ++解释器。
问题是为什么人们使用C ++。很多人都在使用C ++,因为他们想要纯粹的执行速度。为了实现这一目标,编译器以操作系统的首选格式输出本机代码,并尝试在编译的可执行文件中硬编码。最后一部分通常意味着在编译/链接时计算地址。如果你在那时修复一个函数的地址(或者更糟糕的是,内联函数体),那么就不再支持钩子了。
话虽这么说, 方法可以使钩子变得便宜,但它需要编译器扩展并且完全不可移植。 Raymond Chen在博客中介绍了如何在Windows API中实现热补丁。他还建议不要在常规代码中使用它。
答案 4 :(得分:1)
至少在我使用的c ++框架上提供了一组纯虚拟类
class RunManager;
class PhysicsManager;
// ...
每个都定义了一组动作
void PreRunAction();
void RunStartAction()
void RunStopAction();
void PostRunAction();
是NOP,但是用户可以覆盖从Parent类派生的地方。
将它与条件编译相结合(是的,我知道“Yuk!”),你可以得到你想要的东西。
答案 5 :(得分:0)
这不是C ++的事情,但为了完成你提到的一些事情,我在* nix系统中使用了LD_PRELOAD环境变量。这个技术的一个很好的例子是faketime库,它挂钩了时间函数。
答案 6 :(得分:0)
必须有一种方法来实现功能,而不会影响不使用该功能的代码的性能。 C ++的设计原则是您只需为所使用的功能支付性能成本。如果检查每个函数以检查它是否被覆盖,那么插入对于许多C ++项目来说都会慢得令人无法接受。特别是,使其工作,以便没有性能成本,同时仍然允许独立编译被覆盖和覆盖的功能将是棘手的。 如果你只允许编译时覆盖,那么性能更容易(链接器可以处理覆盖地址),但是你要比较ruby和javascript,它可以让你在运行时改变这些东西
因为它会破坏类型系统。如果有人可以覆盖其行为,那么函数是私有还是非虚函数意味着什么呢?
可读性会受到很大影响。任何函数都可能在代码中的其他位置覆盖其行为!理解函数功能所需的上下文越多,找出大型代码库就越困难。 挂钩是一个错误,而不是一个功能。至少如果能够阅读你几个月后写的内容是一项要求。