纯函数都是函子
Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, what should it do now that it has this tag? The simplest way to handle this would be to invoke a
通常,在实现功能时,您不知道某些事件在发生时应如何处理,而您宁愿服从函数或类的用户。 例如,一个XML解析器将从源代码中提取一个标签,现在有了这个标签,该怎么办? 解决此问题的最简单方法是调用
知道如何处理标签的 Callback function that knows how to handle tags. This is exactly how 回调函数。 这正是 SAX (Simple API for XML) style XML parsers work. There are, of course, other reasons for invoking a callback function: implementing logging, SAX (XML的简单API)样式XML解析器的工作方式。 当然,调用回调函数还有其他原因:实现日志记录, enumerating windows and many more. All these are examples of 枚举窗口等。 所有这些都是 event driven programming; when you encounter an event and you call an event handler to handle it. 事件驱动编程的例子; 当您遇到事件并调用事件处理程序来处理它时。In C, if you want to provide a callback mechanism you must implement a callback function and then pass the address of the callback function to the invoker (the code that will call your callback when it's needed). Unfortunately, C style function pointers have a number of drawbacks:
在C语言中,如果要提供回调机制,则必须实现一个回调函数,然后将回调函数的地址传递给调用程序(该代码将在需要时调用回调函数)。 不幸的是,C风格的函数指针有很多缺点:
1. A function contains no instance state, mainly because there is no such thing as multiple instances of a function; there will only ever be one instance and that is global. Sure, it is possible to declare a local static within the function that can retain state between calls, but since a function only has one instance there can only be once instance of the static member and that must be shared between function calls.
1.一个函数不包含实例状态,主要是因为没有一个函数的多个实例; 只会有一个实例是全球性的。 当然,可以在函数内声明一个局部静态变量,该局部静态变量可以保留调用之间的状态,但是由于一个函数只有一个实例,因此只能有一个静态成员实例,并且必须在函数调用之间共享。
A function with local static state
具有局部静态的功能
MyDataClass & foo()
{
static MyDataClass data;
// Do some work
return data;
}
2. If you try to maintain state in the function by using a local static variable the function will not be reentrent, so it cannot be safely called on multiple threads without the additional overhead of thread synchronisation to ensure access to the local static data has mutual exclusion semantics. This effectively means the function can only allow one thread into it at any one time and that will create quite a bottle neck of thread contention (multiple threads all fighting for access to a single resource). Furthermore, if access is required to the static state local variable after the function has finished the caller must continue to block access until the state has either been copied for use or it is no longer required otherwise it'll be read by one thread whilst another is potentially trying to modify it, resulting in a race condition.
2.如果您尝试通过使用局部静态变量来维护函数中的状态,则该函数将不会被重入 ,因此,如果没有线程同步的额外开销来确保相互访问局部静态数据,则无法在多个线程上安全地调用该函数排除语义。 这实际上意味着该函数在任何时候只能允许一个线程进入其中,这将造成相当大的线程争用瓶颈(多个线程都在争夺对单个资源的访问权)。 此外,如果函数完成后需要访问静态局部变量,则调用方必须继续阻止访问,直到该状态被复制以使用或不再需要该状态为止,否则它将被一个线程读取,而另一个线程将读取该状态。正在潜在地试图对其进行修改,从而导致竞争状况 。
A function with local static state using a mutex to attempt to make the function reentrant
使用互斥量具有局部静态状态的函数尝试使函数可重入
MyDataClass & foo(Mutex m)
{
ScopedLock sl(m); // Ensure no other thread can get in here
static MyDataClass data;
// Do some work
return data;
// Note once the scoped lock ends here another thread could enter
// and modify data before the caller has a chance to copy it
// so even this isn't a very good solution, really the mutex should
// be locked by the caller.
}
3. Function pointers do not work very well with templates, if only one signature of the function exists then things will work otherwise the compiler will complain of template instantiation ambiguities that can only be resolved through ugly function pointer casting.
3.函数指针不能很好地与模板配合使用,如果仅存在函数的一个签名,则说明一切正常,否则编译器将抱怨模板实例化的歧义,只能通过丑陋的函数指针转换来解决。
A function pointer casting to resolve template instantiation ambiguities
函数指针强制转换以解决模板实例化的歧义
void callback_func(void *){}
void callback_func(char *){}
template <typename funcT>
void consumer(funcT callback_func)
{}
int main()
{
consumer(callback_func); // ERROR: Which one?
consumer((void (*)(void*))callback_func);
consumer((void (*)(char*))callback_func);
}
4. What if your invoker expects a function that takes only one parameter and you want to use a 3rd party function as the callback, to which you have no source code, and this has a completely different signature to that expected by the invoker? Well, you can wrap the function with another function that adapts the interface but this wrapper will need to have the additional required parameters hard coded into it. What if you want the flexibility of changing what the parameter values are for arbitrary calls to the invoker? Well, for that you will need to write an adaptor for each and every permutation and even then it's still fixed at compile time so it's not easily extensible and becomes quite messy with multiple functions that need to be maintain.
4.如果您的调用方期望一个仅使用一个参数的函数,并且您想使用第三方函数作为回调,而您没有源代码,并且该签名与调用方所期望的签名完全不同,该怎么办? 好的,您可以使用适合接口的另一个函数包装该函数,但是此包装器需要将其他必需的参数硬编码到其中。 如果您想要灵活地更改参数值以用于对调用方的任意调用,该怎么办? 好吧,为此,您将需要为每个排列编写一个适配器,即使那样,它在编译时仍是固定的,因此它不容易扩展,并且由于需要维护的多个功能而变得混乱。
An adaptor using a function
使用功能的适配器
bool third_party_func(int, char, float){ return true; }
template <typename funcT>
void invoker(funcT callback_func)
{
callback_func(int());
}
// C style adaptors
void adaptor_func1(int n)
{
third_party_func(n, 'c', 1.1f); // Hard coded bindings, cannot be changed at runtime
}
void adaptor_func2(int n)
{
third_party_func(n, 'b', 5.9f); // Hard coded bindings, cannot be changed at runtime
}
int main()
{
// C style function has hard coded bindings
invoker(adaptor_func1);
invoker(adaptor_func2);
}
So, is there a better way? Well, now you come to mention it, yes there is! Enter the functor.
那么,有没有更好的方法? 好吧,现在您来提一提,是的! 输入函子 。
What is a functor? Well, simply put it is a function object, and in C-PlusPlus we model a functor using a normal class object. What makes the object a functor is the provision of a function operator, which gives the class object function semantics. The function operator, in its simply canonical for looks like this...
什么是函子? 好吧,简单地说,它是一个函数对象,在C-PlusPlus中,我们使用普通的类对象对仿函数建模。 使对象成为函子的是提供函数运算符,该运算符赋予了类对象函数语义。 函数运算符在其简单的规范中看起来像这样...
The basic canonical form of a functor
函子的基本规范形式
class Functor
{
public:
R operator()(T1, ..., Tn)
{
return R();
}
};
Where R is the return type, T is a parameter type and just like any function the number of parameters is arbitrary. So a more concrete type of a functor that takes 2 int parameters and returns a bool would be as follows...
其中R是返回类型,T是参数类型,就像任何函数一样,参数的数量是任意的。 因此,采用2个int参数并返回bool的函子的更具体类型如下:
A concrete example of a simple functor
一个简单的函子的具体示例
class LessThanFunctor
{
public:
bool operator()(int lhs, int rhs)
{
return lhs < rhs;
}
};
It's pretty clear to see that this simple functor will compare two integers and if the left-hand-side is less than the right-hand-side it will return true, else it will return false.
很明显,这个简单的函子将比较两个整数,如果左侧小于右侧,则将返回true,否则将返回false。
How do we use a functor and how does it differ from a function? Well, as already stated a functor is just a class with function semantics, of course it is still just a class and like all classes it can contain data and function members and instances can be created. What this means is that each instance of the functor object can contain and maintain its own internal state. This state can either be set during construction of the function or after construction. This means the functor can be primed with state before use and it can set its own state during use, which can be extracted after.
我们如何使用函子,它与函数有何不同? 好了,正如已经指出的,函子只是具有函数语义的类,当然它仍然只是一个类,并且像所有类一样,它可以包含数据,函数成员和实例。 这意味着函子对象的每个实例都可以包含并维护其自己的内部状态。 可以在功能构建期间或构建之后设置此状态。 这意味着函子可以在使用前用状态填充,并且可以在使用期间设置其自己的状态,然后可以将其提取。
Lets look at a functor in action. First of all, let's revisit the issue of binding parameters to 3rd party functions to facilitate using them in with an invoker that expects a different signature. With a functor the additional parameters can be loaded into the functor instance when it is created, which can then be bound to the third party call at run-time and not compile time. In fact this is exactly what the standard library functions bind1st and bind2nd do. Look at the example below, notice how much neater the solution is, no need for multiple functions to provide multiple bindings and also note that the bindings provided are passed into the constructor of the functor rather than being hard-coded into it, thus allowing these bindings to be changed at run-time.
让我们看一下实际中的函子。 首先,让我们重新讨论将参数绑定到第三方函数的问题,以方便将其与需要不同签名的调用方一起使用。 使用函子,可以在创建函子实例时将其他参数加载到函子实例中,然后可以在运行时而不是编译时将其绑定到第三方调用。 实际上,这正是标准库函数bind1st和bind2nd所做的。 查看下面的示例,请注意解决方案的整洁程度,不需要多个函数来提供多个绑定,还请注意,提供的绑定将传递到函子的构造函数中,而不是被硬编码到函子中,从而允许这些绑定将在运行时更改。
An adaptor using a functor
使用函子的适配器
bool third_party_func(int, char, float){ return true; }
template <typename funcT>
void invoker(funcT callback_func)
{
callback_func(int());
}
// C++ style adaptor
class adaptor_functor
{
public:
// Initialize runtime bindings
adaptor_functor(char cb, float fb) : cb_(cb), fb_(fb){}
void operator()(int n)
{
third_party_func(cb_, fb_, n);
}
private:
char cb_;
float fb_;
};
int main()
{
// C++ functor has bindings that can be set ar runtime via the functors constructor
invoker(adaptor_functor('a', 2.3f));
invoker(adaptor_functor('z', 0.0f));
}
How about another example? A common usage of a callback function is to provide a user defined logging mechanism for a 3rd party library. The library will callback to your logging callback function, provide it with some details and it is up to you to design a function that will do something useful with those details, like write them to a log file. Furthermore we must record how many times we logged something and how many of these were errors so at the end of the call to the third-party library we can append a count of entries to the log file. Implementing this using a standard C function callback mechanism would require quite a lot of effort, however, using a functor it's pretty simple.
另一个例子呢? 回调函数的常见用法是为第三方库提供用户定义的日志记录机制。 该库将回调您的日志记录回调函数,并为它提供一些详细信息,您可以设计一个函数来对这些细节进行一些有用的操作,例如将它们写入日志文件。 此外,我们必须记录我们记录某事的次数以及其中有多少是错误,因此在调用第三方库的末尾,我们可以将条目计数附加到日志文件中。 使用标准的C函数回调机制来实现此功能将需要大量的精力,但是,使用仿函数非常简单。
A logging functor in action
运行中的测井函子
#include <iostream>
#include <sstream>
#include <string>
#include <ctime>
// Note that this logging class assumes single threading, additional code would be required
// to provide mutual exclusion semantics, which are outside the scope of this article
class LoggingFunctor
{
public:
// Constructor allows user defined output stream
LoggingFunctor(std::ostream & os) :
os_(os), nErrCnt_(0), nLogCnt_(0) {}
// Overload for std::string
void operator()(std::string const & s, bool bErr)
{
// Hand off to overload for char const *
(*this)(s.c_str(), bErr);
}
// The main logging funcion
void operator()(char const * szMsg, bool bErr)
{
// Count log item
++ nLogCnt_;
// Display date & time
time_t t = time(0);
char tbuf[80];
strftime (tbuf,80,"%x %X ",localtime(&t));
os_ << tbuf;
// Is this an error message?
if(bErr)
{
// Count error and display error prefix
++ nErrCnt_;
os_ << "ERROR: ";
}
// NOw log it
os_ << szMsg << std::endl;
}
// Accessors to the log and error count
int GetErrCnt() const { return nErrCnt_; }
int GetLogCnt() const { return nLogCnt_; }
private:
// Non-copyable semantics to prevent accidental copy or assignment
LoggingFunctor(LoggingFunctor const &);
LoggingFunctor operator=(LoggingFunctor const &);
private:
std::ostream & os_;
int nErrCnt_;
int nLogCnt_;
};
template <typename LoggingFunctorT>
void Mock3rdPartyCall(LoggingFunctorT & logger)
{
for(int i = 0 ; i < 25; ++i)
{
// Build a log message
std::stringstream ss;
ss << "Log entry " << i;
// Log it, treat every 3rd iteration as an error
logger(ss.str(), i%3 == 0);
}
}
int main()
{
// Log to stdout for this example
LoggingFunctor loggingFunctor(std::cout);
// Call the mock 3rd party function
Mock3rdPartyCall(loggingFunctor);
std::cout
<< std::endl
<< loggingFunctor.GetLogCnt() << " items logged, "
<< loggingFunctor.GetErrCnt() << " of which were errors." << std::endl;
}
Okay, we've had a couple of concrete example of using functors but I can hear you screaming, "are there any drawbacks?". Well, yes. For a start functors are not C API friendly and cannot really be made to work with an existing function that already expects an old C style function pointer. Other drawbacks? Well, unlike functions, functors have to be instantiated and like any object a functor can throw on construction so additional consideration must be given to ensure code is exception safe. Other than these few issues the use of a C-PlusPlus functor has few downsides and many benefits.
好的,我们有几个使用函子的具体示例,但我听到您在尖叫:“有什么缺点吗?”。 嗯,是。 首先,函子不是C API友好的,并且不能真正使它与已经期望有旧C样式函数指针的现有函数一起使用。 其他弊端? 嗯,与函数不同,函子必须实例化,并且函子可以像其他任何对象一样可以进行构造,因此必须额外考虑以确保代码是异常安全的。 除了这些问题以外,使用C-PlusPlus函子几乎没有弊端,也有很多好处。
So how do you start making use of functors? When writing C-PlusPlus code that uses callbacks it is always a good idea to implement support for functors as well as function pointers. This is exactly what the Standard Template Library does. The code to support both functors and function pointers is quite simple, requiring only the use of a simple template parameter rather than an explicit function pointer type. Since the calling semantics of a functor and a function are identical, the invoker works just as well with either. In template meta-programming parlance we say that a functor models a function concept and, therefore, either a function or a functor can be passed as a template parameter where that parameter represents a function concept. Let's go back to the LessThan functor to see this.
那么如何开始使用函子呢? 编写使用回调的C-PlusPlus代码时,实现对函子和函数指针的支持始终是一个好主意。 这正是标准模板库所做的。 同时支持函子和函数指针的代码非常简单,仅需要使用简单的模板参数,而无需使用显式的函数指针类型。 由于函子和函数的调用语义是相同的,因此调用者对两者的使用都一样。 用模板元编程的话来说,我们说函子为函数概念建模,因此,可以将函数或函子作为模板参数传递,其中该参数表示函数概念。 让我们回到LessThan函子看一下。
How to write an invoker to use either a functor or a function
如何编写调用程序以使用函子或函数
#include <iostream>
class LessThanFunctor
{
public:
bool operator()(int lhs, int rhs)
{
return lhs < rhs;
}
};
bool LessThanFunction(int lhs, int rhs)
{
return lhs < rhs;
}
// To make this work with a function or functor we just use a template parameter
template <typename functorT>
bool Invoker(functorT func, int x, int y)
{
return func(x,y);
}
int main()
{
std::cout
<< "Functor: " << Invoker(LessThanFunctor(), 5, 6)
<< std::endl
<< "Function: " << Invoker(LessThanFunction, 5, 6)
<< std::endl
<< "Functor: " << Invoker(LessThanFunctor(), 7, 6)
<< std::endl
<< "Function: " << Invoker(LessThanFunction, 7, 6)
<< std::endl;
}
You'll find the functor concept is used ubiquitously by the C-PlusPlus STL (Standard Template Library) as well as the Boost libraries. Knowing how to write and use functors is a key success factor in writing generic and reusable code and being able to make use of advanced features of the STL and Boost. They are a tool that should be in any C-PlusPlus programmers toolkit!
您会发现C-PlusPlus STL (标准模板库)以及Boost库普遍使用函子概念。 知道如何编写和使用函子是编写通用和可重用代码以及能够利用STL和Boost的高级功能的关键成功因素。 它们是任何C-PlusPlus程序员工具包中都应该包含的工具!
Further reading: The Function Pointer Tutorials - Introduction to the basics of C and C-PlusPlus Function Pointers, Callbacks and Functors.
进一步阅读: 函数指针教程 -C和C-PlusPlus函数指针,回调和函子的基础知识简介。
翻译自: https://www.experts-exchange.com/articles/849/Function-pointers-vs-Functors.html
纯函数都是函子