为什么函数不是对象?

Why is a function not an object?

我阅读了标准 n4296(草案)§ 1.8 第 7 页:

An object is a region of storage. [ Note: A function is not an object, regardless of whether or not it occupies storage in the way that objects do. —end note ]

我花了几天时间在网上寻找这种排除的充分理由,但没有成功。也许是因为我不完全了解对象。所以:

  1. 为什么函数不是对象?它有何不同?
  2. 这与仿函数(函数对象)有什么关系吗?

很多差异归结为指针和寻址。在 C++¹ 中,指向函数的指针和指向对象的指针是完全不同的东西。

C++ 要求您可以将指向任何对象类型的指针转​​换为指向 void 的指针,然后将其转换回原始类型,结果将等于您开始时使用的指针²。换句话说,不管他们到底是怎么做的,实现必须确保从指针到对象类型到指针到 void 的转换是无损的,所以无论原始是什么,它包含的任何信息都可以重新创建,这样您就可以通过从 T* 转换为 void * 并返回到 T*.

来恢复与开始时相同的指针

不过,对于指向函数的指针,不是 true——如果您使用指向函数的指针,将其转换为 void *,然后再将其转换回来指向函数的指针,您可能会 丢失 过程中的一些信息。您可能无法取回原始指针,取消引用您取回的内容会给您带来未定义的行为(简而言之,不要那样做)。

尽管如此,您 可以 将指向一个函数的指针转换为指向另一种函数类型的指针,然后将该结果转换回原始类型,并且保证结果与开始时相同。

虽然它与手头的讨论不是特别相关,但还有一些其他差异可能值得注意。例如,您可以复制大多数对象--但不能复制 任何 函数。

就与函数对象的关系而言:嗯,除了一点之外真的没有太多关系:函数对象支持看起来像函数调用的 语法- - 但它仍然是一个对象,而不是一个函数。因此,指向函数对象的指针仍然是指向对象的指针。例如,如果您将 1 转换为 void *,然后将其转换回原始类型,您仍然可以保证得到原始指针值(对于指向函数的指针则不会如此) ).

至于为什么 指向函数的指针(至少可能)不同于指向对象的指针:部分原因归结为现有系统。例如,在 MS-DOS(以及其他操作系统)上有四种完全独立的内存模型:小型、中型、紧凑型和大型。小型模型对功能或数据使用 16 位寻址。 Medium 数据使用 16 位地址,代码使用 20 位地址。 Compact 则相反(16 位地址用于代码,20 位地址用于数据)。 Large 用于代码和数据的 20 位地址。因此,无论是在紧凑型还是中型模型中,在指向代码的指针和指向函数的指针之间进行转换确实会并且确实会导致问题。

最近,相当多的 DSP 为代码和数据使用了完全独立的内存总线,并且(与 MS-DOS 内存模型一样)它们通常具有不同的宽度,在两者之间进行转换可能并且确实会丢失信息。


  1. 这些特定的规则从 C 传到 C++,因此在 C 中也是如此,无论其价值如何。
  2. 虽然不是直接需要,但按照事情的运作方式,对于从原始类型到指向 char 的指针的转换以及返回的转换,无论其价值如何,结果几乎都是一样的。

Why a function is not an object? How does it differ?

为了理解这一点,让我们按照所涉及的抽象从下到上移动。所以,你有你的地址space,你可以通过它定义内存的状态,我们必须记住,从根本上说,这就是你操作的状态在.

好的,让我们在抽象方面更进一步。我还没有考虑编程语言强加的任何抽象(如对象、数组等),只是作为一个外行,我想记录一部分内存,让我们称它为Ab1,另一个叫Ab2

从根本上讲,两者都有一个状态,但我打算 manipulate/make 以不同方式使用状态

不同...为什么以及如何?

为什么?

因为我的要求(例如,执行 2 个数字的加法并将结果存储回来)。我将使用 Ab1 作为长期使用状态,使用 Ab2 作为相对较短的使用状态。因此,我将为 Ab1( 以及要添加的 2 个数字 ) 创建一个状态,然后使用该状态填充 Ab2(临时复制它们) 并进一步操作Ab2(添加它们) 并将结果Ab2的一部分保存到Ab1(相加的结果)。 Post Ab2 变得无用,我们 重置 它的状态。

如何?

我需要对这两个部分进行一些管理,以跟踪从 Ab1 中选择哪些词并复制到 Ab2 等等。在这一点上,我意识到我可以让它工作来执行一些简单的操作,但一些严肃的事情需要一个布局规范来管理这个内存。

所以,我寻找这样的管理规范,结果发现存在多种此类规范(其中一些具有内置内存模型,另一些则提供了自己管理内存的灵活性) 更好的设计。事实上,因为他们(甚至没有规定如何直接管理内存)已经成功地定义了这个长期存储的封装以及如何以及何时可以创建和销毁它的规则。

Ab2 也是如此,但他们呈现的方式让我觉得这与 Ab1 有很大不同。事实上,事实证明是这样。他们使用堆栈进行 Ab2 的状态操作,并从堆中为 Ab1 保留内存。 Ab2 一段时间后死亡。(完成执行后)。

此外,您定义如何处理 Ab2 的方式是通过另一个名为 Ab2_Code 的存储部分完成的,并且 Ab1 的规范涉及类似的 Ab1_Code

我会说,这太棒了!我得到了很多便利,让我解决了很多问题。

现在,我仍然是从外行的角度来看,所以我真的经历了这一切的思考过程并不感到惊讶,但如果你自上而下地质疑事情,事情就会变得有点困难透视。(我怀疑你的情况就是这样

顺便说一句,我忘了说 Ab1 正式称为 objectAb2function stackAb1_Codeclass 定义 Ab2_Code 函数定义 代码。

正是由于PL强加的这些差异,你发现它们如此不同。(你的问题

注意:不要把我对Ab1/Object的表示当作一个规则或具体的长存储抽象 - 它是从外行的角度来看。编程语言在管理对象的生命周期方面提供了更大的灵活性。因此,对象可以像 Ab1 一样部署,但它可以更多。

And does this have any relation with the functors (function objects)?

请注意,第一部分答案通常对许多编程语言(包括 C++)有效,这部分必须专门针对 C++(您引用了其规范)。所以你有一个指向函数的指针,你也可以有一个指向对象的指针。它只是 C++ 定义的另一种编程结构。请注意,这是关于让 指针 指向 Ab1Ab2 来操纵它们,而不是让另一个不同的抽象来操作。

你可以在这里阅读它的定义和用法:

C++ Functors - and their uses

让我用更简单的语言(术语)回答问题。

函数包含什么?

它基本上包含做某事的说明。在执行指令时,函数可以临时存储和/或使用一些数据 - 并且可能 return 一些数据。

尽管指令存储在某处 - 这些指令本身不被视为对象。

那么,对象是什么?

通常,对象是包含数据的实体 - 由函数(指令)操作/更改/更新。

为什么不同?

因为计算机的设计方式使得指令不依赖于数据。

为了理解这一点,让我们考虑一下计算器。我们使用计算器进行不同的数学运算。比如说,如果我们想添加一些数字,我们将这些数字提供给计算器。无论数字是多少,计算器都会按照相同的指令以相同的方式将它们相加(如果结果超出计算器的存储容量,它将显示错误 - 但这是因为计算器存储结果的限制(数据),而不是因为它的加法指令)。

计算机的设计方式类似。这就是为什么当您在某些与函数兼容的数据上使用库函数(例如 qsort())时,您会得到与预期相同的结果 - 如果数据不改变,函数的功能不会改变更改 - 因为函数的指令保持不变。

函数与函子的关系

函数是一组指令;在执行它们的同时,可能需要存储一些临时数据。换句话说,一些对象可能是在执行函数时临时创建的。这些临时对象是函子。