指向成员的指针

指向成员函数的指针

“指向成员函数的指针”的类型是否与“指向函数的指针”的类型不同?

有的。

考虑以下函数

int f(char a, float b);

这个函数的类型取决于它是一个普通函数还是某个类的非static成员函数

  • 如果是一个普通函数,其类型是“int (*)(char,float)
  • 如果是Fred类的非static成员函数,其类型是“int (Fred::*)(char,float)

注意:如果它是Fred类的static成员函数,其类型与普通函数相同:“int (*)(char,float)”。

如何将指向成员函数的指针传递给信号处理程序、X事件回调、启动线程/任务的系统调用等?

不要这样做。

因为成员函数在没有对象调用它的情况下是没有意义的,所以你不能直接这样做(如果X Window系统是用C++重写的,它可能会传递对象的引用,而不仅仅是指向函数的指针;当然,这些对象将包含所需的函数以及可能更多的内容)。

作为现有软件的补丁,使用顶级(非成员)函数作为包装器,该包装器接受通过其他技术获取的对象。根据你调用的例程,这种“其他技术”可能微不足道,也可能需要你做一些工作。例如,启动线程的系统调用可能要求你传递一个函数指针和一个void*,这样你就可以在void*中传递对象指针。许多实时操作系统为启动新任务的函数做类似的事情。最坏的情况是你可以将对象指针存储在全局变量中;Unix信号处理程序可能需要这样做(但全局变量通常是不受欢迎的)。无论如何,顶级函数将在对象上调用所需的成员函数。

这是最坏情况(使用全局变量)的一个例子。假设你希望在中断时调用Fred::memberFn()

class Fred {
public:
  void memberFn();
  static void staticMemberFn();  // A static member function can usually handle it
  // ...
};

// Wrapper function uses a global to remember the object:
Fred* object_which_will_handle_signal;

void Fred_memberFn_wrapper()
{
  object_which_will_handle_signal->memberFn();
}

int main()
{
  /* signal(SIGINT, Fred::memberFn); */   // Can NOT do this
  signal(SIGINT, Fred_memberFn_wrapper);  // Okay
  signal(SIGINT, Fred::staticMemberFn);   // Okay usually; see below
  // ...
}

注意:static成员函数不需要实际对象来调用,因此指向static成员函数的指针通常与常规指向函数的指针类型兼容。然而,虽然它可能在大多数编译器上工作,但它实际上必须是一个extern "C"非成员函数才能正确,因为“C链接”不仅包括名称修饰等,还包括调用约定,这在C和C++之间可能有所不同。

当我尝试将成员函数用作中断服务例程时,为什么会不断收到编译错误(类型不匹配)?

这是前两个问题的特例,因此请先阅读前两个答案。

static成员函数有一个隐藏参数,它对应于this指针。this指针指向对象的实例数据。系统中的中断硬件/固件无法提供this指针参数。你必须使用“普通”函数(非类成员)或static成员函数作为中断服务例程。

一个可能的解决方案是使用static成员作为中断服务例程,并让该函数在某处查找应该在中断时调用的实例/成员对。因此,效果是成员函数在中断时被调用,但由于技术原因,你需要首先调用一个中间函数。

我为什么在获取C++函数地址时遇到问题?

简短的回答:如果你尝试将其存储到(或将其作为)指向函数的指针,那么这就是问题所在——这是前一个FAQ的推论。

长回答:在C++中,成员函数有一个隐式参数,它指向对象(成员函数内部的this指针)。普通的C函数可以被认为是与成员函数有不同的调用约定,因此它们的指针类型(指向成员函数的指针与指向函数的指针)是不同且不兼容的。C++引入了一种新的指针类型,称为指向成员的指针,只能通过提供对象来调用。

注意:不要尝试将指向成员函数的指针“强制转换”为指向函数的指针;结果是未定义的,并且可能灾难性。例如,指向成员函数的指针要求包含相应函数的机器地址。如上例所述,如果你有一个指向常规C函数的指针,请使用顶级(非成员)函数或static(类)成员函数。

创建指向成员的指针时如何避免语法错误?

使用typedef

是的,没错,我知道:与众不同。你很聪明。你可以在没有typedef的情况下完成这些事情。唉。我收到过许多人的邮件,他们和你一样,拒绝接受本FAQ的简单建议。他们浪费了数小时的时间,而10秒的typedef就可以简化他们的生活。此外,面对现实吧,你写的代码不只是你自己能读;你希望你的代码也能被其他人阅读——当他们疲惫时——当他们有自己的截止日期和自己的挑战时。那么,为什么故意给自己和他人制造困难呢?聪明点:使用typedef

这是一个示例类

class Fred {
public:
  int f(char x, float y);
  int g(char x, float y);
  int h(char x, float y);
  int i(char x, float y);
  // ...
};

typedef很简单

typedef  int (Fred::*FredMemFn)(char x, float y);  // Please do this!

就是这样!FredMemFn是类型名称,该类型的指针指向Fred中接受(char,float)的任何成员,例如Fredfghi

然后声明成员函数指针就变得轻而易举

int main()
{
  FredMemFn p = &Fred::f;
  // ...
}

声明接收成员函数指针的函数也轻而易举

void userCode(FredMemFn p)
{ /*...*/ }

声明返回成员函数指针的函数也轻而易举

FredMemFn userCode()
{ /*...*/ }

所以请使用typedef。否则,不要给我发电子邮件抱怨你的成员函数指针问题!

如何在使用指向成员函数的指针调用成员函数时避免语法错误?

如果你可以使用实现即将到来的C++17标准相应部分的编译器和标准库,请使用std::invoke。否则,请使用#define宏。

拜托了。

求求你了。

我收到太多来自拒绝接受此建议的困惑人士的电子邮件。这太简单了。我知道,你不需要std::invoke或宏,你交谈过的专家可以在没有它们的情况下做到,但请不要让你的自尊心阻碍重要的事情:金钱。其他程序员需要阅读/维护你的代码。是的,我知道:你比其他人聪明;没问题。你很棒;也没问题。但不要给你的代码增加不必要的复杂性。

使用std::invoke非常简单。注意:FredMemFn指向成员类型的typedef

void userCode(Fred& fred, FredMemFn p)  // Use a typedef for pointer-to-member types
{
  int ans = std::invoke(p, fred, 'x', 3.14);
  // Would normally be: int ans = (fred.*p)('x', 3.14);

  // ...
}

如果你不能使用std::invoke,那么在这个特殊情况下,请自相矛盾地使用#define宏来降低维护成本。

通常我不喜欢#define,但是你对于指向成员的指针应该使用它们,因为它们改善了这类代码的可读性和可写性。)

宏很简单

#define CALL_MEMBER_FN(object,ptrToMember)  ((object).*(ptrToMember))

使用宏也很简单。注意:FredMemFn指向成员类型的typedef

void userCode(Fred& fred, FredMemFn p)  // Use a typedef for pointer-to-member types
{
  int ans = CALL_MEMBER_FN(fred,p)('x', 3.14);
  // Would normally be: int ans = (fred.*p)('x', 3.14);

  // ...
}

使用std::invoke或此宏是一个好主意的原因是,成员函数调用通常比上面给出的简单示例要复杂得多。可读性和可写性的差异是显著的。comp.lang.c++不得不忍受数百个来自困惑的程序员的帖子,他们无法正确地写出语法。如果他们使用std::invoke或上述宏,几乎所有这些错误都会消失。

注意:#define宏以4种不同的方式是邪恶的邪恶#1邪恶#2邪恶#3邪恶#4。但它们有时仍然有用。但你使用它们后仍然应该有一种模糊的羞耻感。

如何创建和使用指向成员函数的指针数组?

同时使用typedefstd::invoke之前描述的#define宏,你就完成了90%。

步骤1:创建一个typedef

class Fred {
public:
  int f(char x, float y);
  int g(char x, float y);
  int h(char x, float y);
  int i(char x, float y);
  // ...
};

// FredMemFn points to a member of Fred that takes (char,float)
typedef  int (Fred::*FredMemFn)(char x, float y);

步骤2:如果你没有std::invoke,则创建一个#define

#define CALL_MEMBER_FN(object,ptrToMember)  ((object).*(ptrToMember))

现在你的指向成员函数的指针数组就很直接了

FredMemFn a[] = { &Fred::f, &Fred::g, &Fred::h, &Fred::i };

并且你使用其中一个成员函数指针也很直接

void userCode(Fred& fred, int memFnNum)
{
  // Assume memFnNum is between 0 and 3 inclusive:
  std::invoke(a[memFnNum], fred, 'x', 3.14);
}

或者如果你没有std::invoke

void userCode(Fred& fred, int memFnNum)
{
  // Assume memFnNum is between 0 and 3 inclusive:
  CALL_MEMBER_FN(fred, a[memFnNum]) ('x', 3.14);
}

注意:#define宏以4种不同的方式是邪恶的邪恶#1邪恶#2邪恶#3邪恶#4。但它们有时仍然有用。感到羞耻,感到内疚,但是当一个像宏这样的邪恶构造能够改善你的软件时,使用它

如何声明指向const成员函数的指向成员函数的指针?

简短的回答:当你使用typedef声明成员函数指针类型时,在)的右侧添加一个const

例如,假设你需要一个指向成员函数的指针,它指向Fred::fFred::gFred::h

class Fred {
public:
  int f(int i) const;
  int g(int i) const;
  int h(int j) const;
  // ...
};

然后当你使用typedef声明成员函数指针类型时,它应该看起来像这样

// FredMemFn points to a const member-function of Fred that takes (int)
typedef  int (Fred::*FredMemFn)(int) const;
                                     ↑↑↑↑↑ // Points only to member functions decorated with const

就是这样!

然后你可以像正常一样声明/传递/返回成员函数指针

void foo(FredMemFn p)  // Pass a member-function pointer
{ /*...*/ }

FredMemFn bar()        // Return a member-function pointer
{ /*...*/ }

void baz()
{
  FredMemFn p = &Fred::f;  // Declare a member-function pointer
  FredMemFn a[10];         // Declare an array of member-function pointers
  // ...
}

.*->*运算符有什么区别?

如果你对成员函数指针调用使用std::invoke或宏,你就不需要理解这个。哦,对了,在这种情况下使用std::invoke或宏。我有没有提到你应该在这种情况下使用std::invoke或宏??!?

但是如果你真的想避免使用std::invoke或宏,唉,呻吟,好吧,就是这样:当左侧参数是对对象的引用时使用.*,当它是指向对象的指针时使用->*

例如

class Fred { /*...*/ };

typedef  int (Fred::*FredMemFn)(int i, double d);  // use a typedef!!! please!!!

void sample(Fred x, Fred& y, Fred* z, FredMemFn func)
{
  x.*func(42, 3.14);
  y.*func(42, 3.14);
  z->*func(42, 3.14);
}

但是请考虑改用std::invoke或宏

void sample(Fred x, Fred& y, Fred* z, FredMemFn func)
{
  std::invoke(func, x, 42, 3.14);
  std::invoke(func, y, 42, 3.14);
  std::invoke(func, *z, 42, 3.14);
}

或者

void sample(Fred x, Fred& y, Fred* z, FredMemFn func)
{
  CALL_MEMBER_FN(x,func)(42, 3.14);
  CALL_MEMBER_FN(y,func)(42, 3.14);
  CALL_MEMBER_FN(*z,func)(42, 3.14);
}

之前所讨论的,实际调用通常比这里简单的调用复杂得多,因此使用std::invoke或宏通常会提高代码的可写性和可读性。

我可以将指向成员函数的指针转换为void*吗?

不!

class Fred {
public:
  int f(char x, float y);
  int g(char x, float y);
  int h(char x, float y);
  int i(char x, float y);
  // ...
};

// FredMemFn points to a member of Fred that takes (char,float)
typedef  int (Fred::*FredMemFn)(char x, float y);

#define CALL_MEMBER_FN(object,ptrToMember)  ((object).*(ptrToMember))

int callit(Fred& o, FredMemFn p, char x, float y)
{
  return CALL_MEMBER_FN(o,p)(x, y);
}

int main()
{
  FredMemFn p = &Fred::f;
  void* p2 = (void*)p;                  // ← illegal!!
  Fred o;
  callit(o, p, 'x', 3.14f);             // okay
  callit(o, FredMemFn(p2), 'x', 3.14f); // might fail!!
  // ...
}

技术细节:指向成员函数的指针和指向数据的指针不一定以相同的方式表示。指向成员函数的指针可能是一个数据结构,而不是单个指针。考虑一下:如果它指向一个虚函数,它实际上可能不是指向一块静态可解析的代码,因此它甚至可能不是一个正常的地址——它可能是某种不同的数据结构。

不要在上面似乎有效的你特定编译器在特定操作系统上的特定版本的情况下给我发电子邮件。我不在乎。这是非法的,就这样。

我可以将函数指针转换为void*吗?

不!

int f(char x, float y);
int g(char x, float y);

typedef int(*FunctPtr)(char,float);

int callit(FunctPtr p, char x, float y)
{
  return p(x, y);
}

int main()
{
  FunctPtr p = f;
  void* p2 = (void*)p;              // ← illegal!!
  callit(p, 'x', 3.14f);            // okay
  callit(FunctPtr(p2), 'x', 3.14f); // might fail!!
  // ...
}

技术细节:void*指针是指向数据的指针,函数指针指向函数。语言不要求函数和数据位于同一地址空间,因此,通过示例而非限制,在将它们放置在不同地址空间的体系结构上,两种不同的指针类型将不可比较。

不要在上面似乎有效的你特定编译器在特定操作系统上的特定版本的情况下给我发电子邮件。我不在乎。这是非法的,就这样。

我需要类似函数指针的东西,但需要更大的灵活性和/或线程安全性;还有其他方法吗?

使用函数对象(functionoid)。

什么是函数对象(functionoid),我为什么要使用它?

函数对象是加强版的函数。函数对象比函数严格地更强大,这种额外的能力解决了一些(并非所有)在使用函数指针时通常面临的挑战。

让我们举一个使用传统函数指针的例子,然后我们将这个例子转换为函数对象。传统的函数指针概念是有一堆兼容的函数

int funct1( /*...params...*/ ) { /*...code...*/ }
int funct2( /*...params...*/ ) { /*...code...*/ }
int funct3( /*...params...*/ ) { /*...code...*/ }

然后你通过函数指针访问它们

typedef int(*FunctPtr)( /*...params...*/ );

void myCode(FunctPtr f)
{
  // ...
  f( /*...args-go-here...*/ );
  // ...
}

有时人们会创建这些函数指针的数组

FunctPtr array[10];
array[0] = funct1;
array[1] = funct1;
array[2] = funct3;
array[3] = funct2;
// ...

在这种情况下,他们通过访问数组来调用函数

array[i]( /*...args-go-here...*/ );

使用函数对象,你首先创建一个具有纯虚方法的基类

class Funct {
public:
  virtual int doit(int x) = 0;
  virtual ~Funct() = 0;
};

inline Funct::~Funct() { }  // defined even though it's pure virtual; it's faster this way; trust me

然后不是三个函数,而是创建三个派生类

class Funct1 : public Funct {
public:
  virtual int doit(int x) { /*...code from funct1...*/ }
};

class Funct2 : public Funct {
public:
  virtual int doit(int x) { /*...code from funct2...*/ }
};

class Funct3 : public Funct {
public:
  virtual int doit(int x) { /*...code from funct3...*/ }
};

然后,不是传递函数指针,而是传递一个Funct*。我将创建一个名为FunctPtrtypedef,仅仅是为了使其余代码与旧式方法相似

typedef Funct* FunctPtr;

void myCode(FunctPtr f)
{
  // ...
  f->doit( /*...args-go-here...*/ );
  // ...
}

你可以以几乎相同的方式创建它们的数组

FunctPtr array[10];
array[0] = new Funct1( /*...ctor-args...*/ );
array[1] = new Funct1( /*...ctor-args...*/ );
array[2] = new Funct3( /*...ctor-args...*/ );
array[3] = new Funct2( /*...ctor-args...*/ );
// ...

这给了我们关于函数对象严格优于函数指针的第一个线索:函数对象方法具有可以传递给构造函数(如上所示的...ctor-args...)的参数,而函数指针版本则没有。将函数对象视为冻干的函数调用(强调“调用”一词)。与指向函数的指针不同,函数对象(概念上)是指向一个部分调用的函数。暂时想象一种技术,允许你向函数传递部分而不是所有参数,然后让你冻干该(部分完成的)调用。假设这种技术返回某种指向该冻干的部分完成函数调用的神奇指针。然后你稍后使用该指针传递剩余的参数,系统神奇地将你的原始参数(已冻干)与函数在冻干之前计算的任何局部变量结合起来,将所有这些与新传递的参数结合起来,并从冻干时中断的地方继续执行函数。这听起来像科幻小说,但从概念上讲,函数对象可以让你做到这一点。此外,它们允许你反复地用各种不同的“剩余参数”来“完成”那个冻干的函数调用,随心所欲。此外,它们允许(不要求)你在被调用时改变冻干状态,这意味着函数对象可以记住从一个调用到下一个调用的信息。

让我们回到现实,我们将举几个例子来解释所有这些天书到底是什么意思。

假设原始函数(以旧式函数指针风格)接受略有不同的参数。

int funct1(int x, float y)
{ /*...code...*/ }

int funct2(int x, const std::string& y, int z)
{ /*...code...*/ }

int funct3(int x, const std::vector<double>& y)
{ /*...code...*/ }

当参数不同时,旧式的函数指针方法很难使用,因为调用者不知道要传递哪些参数(调用者只有指向函数的指针,而不是函数的名称,或者当参数不同时,其参数的数量和类型)(不要给我写关于此的电子邮件;是的,你可以做到,但你必须倒立并做一些麻烦的事情;但不要给我写关于此的电子邮件——而是使用函数对象)。

有了函数对象,情况至少在某些时候会好得多。由于函数对象可以被认为是一个冻干的函数调用,只需取那些不常见的参数,例如我称之为y和/或z的参数,并将它们作为相应构造函数的参数。你也可以将常见参数(在这种情况下是int类型的x)传递给构造函数,但你不必这样做——你可以选择将其传递给纯虚doit()方法。我假设你想将x传递给doit(),将y和/或z传递给构造函数

class Funct {
public:
  virtual int doit(int x) = 0;
};

然后不是三个函数,而是创建三个派生类

class Funct1 : public Funct {
public:
  Funct1(float y) : y_(y) { }
  virtual int doit(int x) { /*...code from funct1...*/ }
private:
  float y_;
};

class Funct2 : public Funct {
public:
  Funct2(const std::string& y, int z) : y_(y), z_(z) { }
  virtual int doit(int x) { /*...code from funct2...*/ }
private:
  std::string y_;
  int z_;
};

class Funct3 : public Funct {
public:
  Funct3(const std::vector<double>& y) : y_(y) { }
  virtual int doit(int x) { /*...code from funct3...*/ }
private:
  std::vector<double> y_;
};

现在你看到当创建函数对象数组时,构造函数的参数被冻干到函数对象中

FunctPtr array[10];

array[0] = new Funct1(3.14f);

array[1] = new Funct1(2.18f);

std::vector<double> bottlesOfBeerOnTheWall;
bottlesOfBeerOnTheWall.push_back(100);
bottlesOfBeerOnTheWall.push_back(99);
// ...
bottlesOfBeerOnTheWall.push_back(1);
array[2] = new Funct3(bottlesOfBeerOnTheWall);

array[3] = new Funct2("my string", 42);

// ...

因此,当用户在这些函数对象之一上调用doit()时,他提供了“剩余”参数,并且该调用概念上将传递给构造函数的原始参数与传递给doit()方法的参数结合起来

array[i]->doit(12);

正如我前面暗示的,函数对象的一个优点是,你可以在数组中拥有多个Funct1实例,并且这些实例可以冻结不同的参数。例如,array[0]array[1]都是Funct1类型,但是array[0]->doit(12)的行为将与array[1]->doit(12)的行为不同,因为行为将取决于传递给doit()的12和传递给构造函数的参数。

如果我们将示例从函数对象数组更改为局部函数对象,则函数对象的另一个好处就显而易见了。为了铺垫,让我们回到传统的函数指针方法,并想象你正在尝试将比较函数传递给sort()binarySearch()例程。sort()binarySearch()例程被称为childRoutine(),比较函数指针类型被称为FunctPtr

void childRoutine(FunctPtr f)
{
  // ...
  f( /*...args...*/ );
  // ...
}

然后不同的调用者会根据他们认为最好的方式传递不同的函数指针

void myCaller()
{
  // ...
  childRoutine(funct1);
  // ...
}

void yourCaller()
{
  // ...
  childRoutine(funct3);
  // ...
}

我们可以轻松地将这个例子转换为使用函数对象的例子

void childRoutine(Funct& f)
{
  // ...
  f.doit( /*...args...*/ );
  // ...
}

void myCaller()
{
  // ...
  Funct1 funct( /*...ctor-args...*/ );
  childRoutine(funct);
  // ...
}

void yourCaller()
{
  // ...
  Funct3 funct( /*...ctor-args...*/ );
  childRoutine(funct);
  // ...
}

以此为例,我们可以看到函数对象相对于函数指针的两个优点。上面描述的“构造函数参数”优点,以及函数对象能够以线程安全的方式在调用之间维护状态的事实。对于普通的函数指针,人们通常通过静态数据在调用之间维护状态。然而,静态数据本质上不是线程安全的——静态数据在所有线程之间共享。函数对象方法为你提供了本质上线程安全的东西,因为代码最终会拥有线程局部数据。实现很简单:将旧式的静态数据更改为函数对象this对象内部的实例数据成员,然后,数据不仅是线程局部的,而且在递归调用时也是安全的:对yourCaller()的每次调用都将有其自己独特的Funct3对象,以及其自己独特的实例数据。

请注意,我们有所得而无所失。如果你想要线程全局数据,函数对象也可以为你提供:只需将其从函数对象this对象内部的实例数据成员更改为函数对象类中的静态数据成员,甚至更改为局部范围静态数据。你不会比使用函数指针更好,但也不会更糟。

函数对象方法提供了第三种旧式方法不具备的选项:函数对象允许调用者决定他们是否需要线程局部或线程全局数据。在需要线程全局数据的情况下,他们将负责使用锁,但至少他们有选择。这很容易

void callerWithThreadLocalData()
{
  // ...
  Funct1 funct( /*...declare ctor args here...*/ );
  childRoutine(funct);
  // ...
}

void callerWithThreadGlobalData()
{
  // ...
  static Funct1 funct( /*...declare ctor args here...*/ );  // The static is the only difference
  childRoutine(funct);
  // ...
}

函数对象并不能解决创建灵活软件时遇到的所有问题,但它们比函数指针严格地更强大,并且至少值得评估。事实上,你可以很容易地证明函数对象没有失去任何函数指针的强大功能,因为你可以想象旧式的函数指针方法等同于拥有一个全局(!)函数对象。既然你总是可以创建一个全局函数对象,你并没有失去任何优势。证毕。

你能让函数对象比普通函数调用更快吗?

是的。

如果你有一个小型的函数对象,并且在现实世界中这很常见,那么函数调用的成本可能会比函数对象完成工作的成本高。在上一个FAQ中,函数对象是使用虚函数实现的,通常会让你付出函数调用的代价。另一种方法是使用模板

下面的例子在精神上与上一个FAQ中的例子相似。我将doit()重命名为operator()(),以提高调用者代码的可读性,并允许有人传递普通的函数指针

class Funct1 {
public:
  Funct1(float y) : y_(y) { }
  int operator()(int x) { /*...code from funct1...*/ }
private:
  float y_;
};

class Funct2 {
public:
  Funct2(const std::string& y, int z) : y_(y), z_(z) { }
  int operator()(int x) { /*...code from funct2...*/ }
private:
  std::string y_;
  int z_;
};

class Funct3 {
public:
  Funct3(const std::vector<double>& y) : y_(y) { }
  int operator()(int x) { /*...code from funct3...*/ }
private:
  std::vector<double> y_;
};

这种方法与上一个FAQ中的方法不同之处在于,函数对象在编译时而不是运行时“绑定”到调用者。可以将其视为传递一个参数:如果你在编译时知道最终想要传递哪种函数对象,那么你可以使用上述技术,并且,至少在典型情况下,可以通过让编译器在调用者内部内联展开函数对象代码来获得速度优势。这是一个例子

template <typename FunctObj>
void myCode(FunctObj f)
{
  // ...
  f( /*...args-go-here...*/ );
  // ...
}

当编译器编译上述代码时,它可能会内联展开调用,这可能会提高性能。

以下是调用上述函数的一种方式

void blah()
{
  // ...
  Funct2 x("functionoids are powerful", 42);
  myCode(x);
  // ...
}

旁白:正如上面第一段所暗示的,你也可以传入普通函数的名称(尽管当调用者使用它们时,你可能会承担函数调用的成本)

void myNormalFunction(int x);

void blah()
{
  // ...
  myCode(myNormalFunction);
  // ...
}

函数对象(functionoid)和仿函数(functor)有什么区别?

函数对象(functionoid)是一个具有一个主要方法的对象。它基本上是C语言风格函数(如printf())的面向对象扩展。当函数有多个入口点(即,多个“方法”)和/或需要在线程安全的方式下在调用之间维护状态时,就会使用函数对象(C语言风格的在调用之间维护状态的方法是向函数添加一个局部“静态”变量,但在多线程环境中这样做极其不安全)。

仿函数(functor)是函数对象的一种特殊情况:它是一个其主要方法是“函数调用运算符”operator()()的函数对象。由于它重载了函数调用运算符,代码可以使用与函数调用相同的语法来调用其主要方法。例如,如果“foo”是一个仿函数,要调用“foo”对象上的“operator()()”方法,可以使用“foo()”。这样做的好处在于模板,因为模板可以有一个模板参数,该参数将用作函数,并且此参数可以是函数名称或仿函数对象。作为仿函数对象具有性能优势,因为“operator()()”方法可以内联(而如果你传递函数的地址,它必然是不可内联的)。

这对于排序容器上的“比较”函数非常有用。在C语言中,比较函数总是通过指针传递(例如,参见“qsort()”的签名),但在C++中,参数可以作为函数指针或仿函数对象的名称传入,结果是C++中的排序容器在某些情况下可能比C中等效的容器快得多(并且绝不会慢)。

由于Java没有与模板类似的东西,因此它必须对所有这些东西使用动态绑定,而动态绑定必然意味着函数调用。通常这不是什么大问题,但在C++中,我们希望实现极高性能的代码。也就是说,C++奉行“只为使用而付费”的理念,这意味着语言绝不能武断地施加任何超出物理机器所能执行的开销(当然,程序员可以选择使用动态绑定等技术,这些技术通常会为了灵活性或其他“性”而施加一些开销,但这取决于设计者和程序员来决定他们是否需要这些构造的好处(和成本))。