为什么函数指针不被认为是面向对象的?

时间:2011-04-20 17:45:23

标签: c# .net oop language-agnostic

在C#语言规范中,它明确指出:

  

代表与概念类似   一些中找到的函数指针   其他语言,但不像功能   指针,代表是   面向对象和类型安全。

我理解委托需要比指针更灵活,因为.NET会移动内存。这是我所知道的唯一区别,但我不确定这会如何将代表转变为OO概念......?

是什么让函数指针不面向对象?指针和函数指针是否等效?

6 个答案:

答案 0 :(得分:32)

嗯,维基百科说“面向对象”意味着使用“数据抽象,封装,消息传递,模块化,多态和继承等功能”。缺乏更好的定义,让我们继续。

函数指针不包含数据,它们不封装实现细节,它们既不发送也不接收消息,它们不是模块化的,它们通常不以多态方式使用(尽管我认为它们理论上可以是协变的返回和形式参数类型中的逆变量,因为委托现在在C#4中,并且它们不参与继承层次结构。他们不是自我描述的;你不能问一个函数指针的类型,因为它没有。

相比之下,代表们捕获数据 - 他们抓住接收器。它们支持消息传递,因为您可以通过调用其ToString或GetType或Invoke或BeginInvoke方法来“告知”委托,以告诉它执行某些操作,并使用结果返回“消息”。如果您选择,则可以将代理类型限制为某些可访问性域。它们是具有元数据的自描述对象,并​​且在运行时知道它们自己的类型。他们可以与其他代表合并。它们可以多态地用作System.MulticastDelegate或System.Delegate,它们是从中继承的类型。并且它们可以多态地使用,因为在C#4委托类型中它们的返回和参数类型可能是协变的和逆变的。

答案 1 :(得分:2)

我相信这是因为,当你持有一个成员方法的委托时,OO框架“知道”你持有对持有对象的引用,而对于函数指针,首先函数不一定是成员方法,其次,如果函数是成员方法,OO框架不知道它必须防止拥有对象被释放。

答案 2 :(得分:1)

函数指针只是内存地址。

委托是具有方法和属性的对象:
-BeginInvoke
-DynamicInvoke
-Invoke
- 方法
- 目标

答案 3 :(得分:1)

我将用C ++示例解释,因为它是一种存在此问题的语言(并以另一种方式解决)。

仅仅是一个函数指针只保存一个函数的地址,没有别的。

考虑功能

void f(int x) { return; }

现在,声明并指定一个简单的函数指针,如下所示:

void (*fptr)(int) = &f;

你可以简单地使用它:

foo(5); // calls f(5)

但是,在面向对象语言中,我们通常处理成员函数,而不是自由函数。这就是事情变得令人讨厌的地方。考虑以下课程:

class C { void g(int x) { return; } };

声明一个指向C :: g的函数指针是这样的:

void (*C::gptr)(int) = &C::g;

我们需要不同语法的原因是成员函数具有隐藏的this参数,因此它们的签名是不同的。

出于同样的原因,调用它们是有问题的。 this参数需要一个值,这意味着您需要一个实例。调用指向成员函数的指针是这样的:

C c;
(c.*gptr)(5);  // calls c.g(5);

除了奇怪的语法之外,真正的问题是当你真的只想传递一件事时,你需要将对象与函数指针一起传递。

显而易见的想法是将两者封装起来,这就是代理人的意思。这就是为什么委托被认为是更多的OOP。我不知道为什么它被认为更加类型安全(可能是因为你可以将函数指针强制转换为void *)。

BTW C ++中的C ++解决方案是从Boost采用的。它被称为std::functionstd::bind,其工作方式如下:

std::function<void (C*, int)> d = std::bind(&c::g, &c);
d(5); // calls c.g(5);

答案 4 :(得分:0)

除非您明确地传递它,否则函数指针可能不知道它所属的实例 - 所有函数指针都是静态成员。另一方面,委托可以是类的常规成员,并且在调用委托时将使用对象的正确实例。

答案 5 :(得分:0)

假设有人想要设计一个通用anyprintf方法,该方法可以表现为fprintfsprintfcprintf [带有颜色支持的控制台printf]。一种方法是让它接受一个接受void*char以及void*和va_list的函数;然后它应该为输出的每个字符调用传入的函数,传递它提供的指针和要输出的字符。

鉴于这样的功能,可以通过以下方式实现vsprintffprintf [忽略它们的返回值]:

void fprint_function(void* data, char ch) { fputc( (FILE*)data, ch); }
void sprint_function(void* data, char ch) { char**p = (char**)data; *((*p)++) = ch; }
void fprint_function(void* data, char ch) { cputchar( ch); }
void vfprintf(FILE *f, va_list vp, const char *fmt, va_list vp)
{
  vsanyprintf(fprint_function, (void*)f, st, vp);
}
void vsprintf(char *st, va_list vp, const char *fmt, va_list vp)
{
  vsanyprintf(fprint_function, (void*)f, st, vp);
}
void vcprintf(va_list vp, const char *fmt, va_list vp)
{
  vsanyprintf(cprint_function, (void*)0, st, vp);
}

有效地,函数指针和void*的组合表现为一种方法。遗憾的是,编译器无法确保在void*中传递的数据具有所提供函数所期望的形式。 C ++和其他面向对象的语言添加了这种类型一致性的编译时验证。