J'ai lu dans les normes n4296 (Draft) § 1.8 page 7:
Un objet est une région de stockage. [Remarque: Une fonction n'est pas un objet, qu'il occupe ou non le stockage de la même manière que les objets. —Fin note]
J'ai passé quelques jours sur le net à chercher une bonne raison pour une telle exclusion, sans succès. Peut-être parce que je ne comprends pas bien les objets. Donc:
Une grande partie de la différence réside dans les pointeurs et l'adressage. En C++ ¹, les pointeurs vers les fonctions et les pointeurs vers les objets sont des types de choses strictement séparés.
C++ nécessite que vous puissiez convertir un pointeur vers n'importe quel type d'objet en un pointeur vers void
, puis le reconvertir au type d'origine, et le résultat sera égal au pointeur avec lequel vous avez commencé². En d'autres termes, quelle que soit la façon dont ils le font, l'implémentation doit garantir qu'une conversion du type pointeur-objet en pointeur-void est sans perte, donc quel que soit l'original, quelles que soient les informations qu'il contenait, être recréé afin que vous puissiez récupérer le même pointeur que vous avez commencé par la conversion de T*
à void *
et retour à T*
.
C'est pas vrai avec un pointeur sur une fonction - si vous prenez un pointeur sur une fonction, convertissez-le en void *
, puis le reconvertir en un pointeur sur une fonction, vous pouvez perdre certaines informations dans le processus. Il se peut que vous ne récupériez pas le pointeur d'origine, et le fait de déréférencer ce que vous récupérez vous donne un comportement indéfini (en bref, ne faites pas cela).
Pour ce que ça vaut, vous pouvez, cependant, convertissez un pointeur en une fonction en un pointeur en un autre type de fonction, puis convertissez ce résultat en retour au type d'origine, et vous êtes assuré que le le résultat est le même que vous avez commencé avec.
Bien que cela ne soit pas particulièrement pertinent pour la discussion en cours, il existe quelques autres différences qui méritent d'être notées. Par exemple, vous pouvez copier la plupart des objets - mais vous ne pouvez pas copier les fonctions any.
En ce qui concerne la relation avec les objets fonction: eh bien, il n'y en a vraiment pas beaucoup au-delà d'un point: un objet fonction prend en charge syntaxe qui ressemble à un appel de fonction - mais c'est toujours un objet, pas une fonction. Ainsi, un pointeur sur un objet fonction est toujours un pointeur sur un objet. Si, par exemple, vous en convertissez un en void *
, puis le reconvertir au type d'origine, vous êtes toujours assuré que vous récupérez la valeur du pointeur d'origine (ce qui ne serait pas vrai avec un pointeur sur une fonction).
Quant à pourquoi les pointeurs vers les fonctions sont (au moins potentiellement) différents des pointeurs vers les objets: une partie en revient aux systèmes existants. Par exemple, sur MS-DOS (entre autres), il y avait quatre modèles de mémoire entièrement distincts: petit, moyen, compact et grand. Le petit modèle utilisait un adressage 16 bits pour les fonctions ou les données. Le support utilise des adresses 16 bits pour les données et des adresses 20 bits pour le code. Compact inversé cela (adresses 16 bits pour le code, adresses 20 bits pour les données). Grandes adresses 20 bits utilisées pour le code et les données. Ainsi, dans les modèles compacts ou moyens, la conversion entre des pointeurs en code et des pointeurs en fonctions pouvait vraiment et a entraîné des problèmes.
Plus récemment, un bon nombre de DSP ont utilisé des bus mémoire entièrement séparés pour le code et pour les données et (comme avec les modèles de mémoire MS-DOS), ils étaient souvent de largeurs différentes, la conversion entre les deux pouvait et a effectivement fait perdre des informations.
char
et inversement, pour tout ce qui vaut.Pourquoi une fonction n'est pas un objet? Comment est-ce différent?
Pour comprendre cela, passons de bas en haut en termes d'abstractions impliquées. Donc, vous avez votre espace d'adressage à travers lequel vous pouvez définir l'état de la mémoire et nous devons nous rappeler que, fondamentalement, tout dépend de cet état sur lequel vous opérez .
D'accord, allons un peu plus haut en termes d'abstractions. Je ne prends pas encore sur les abstractions imposées par un langage de programmation ( comme un objet, un tableau, etc.) mais simplement en tant que profane, je veux garder une trace d'une partie de la mémoire, laisse appelez-le Ab1
et un autre appelé Ab2
.
Les deux ont fondamentalement un état mais j'ai l'intention de manipuler/utiliser l'état différemment.
Pourquoi ?
En raison de mes exigences (pour effectuer l'ajout de 2 numéros et stocker le résultat, par exemple). J'utiliserai use Ab1
comme état d'utilisation long et Ab2
comme état d'utilisation relativement plus court. Je vais donc créer un état pour Ab1
( avec les 2 chiffres à ajouter), puis utilisez cet état pour remplir certains états de Ab2
( copiez-les temporairement) et effectuez d'autres manipulations de Ab2
( ajoutez-les) et enregistrez une partie du résultat Ab2
à Ab1
( le résultat ajouté). Publier ce Ab2
devient inutile et nous réinitialisons son état.
Comment?
Je vais avoir besoin d'une certaine gestion des deux parties pour garder une trace des mots à choisir parmi Ab1
et copiez vers Ab2
etc. À ce stade, je me rends compte que je peux le faire fonctionner pour effectuer des opérations simples, mais quelque chose de grave nécessitera une spécification définie pour gérer cette mémoire.
Donc, je cherche une telle spécification de gestion et il s'avère qu'il existe une variété de ces spécifications ( avec certains ayant un modèle de mémoire intégré, d'autres offrent une flexibilité pour gérer la mémoire vous-même) avec une meilleure conception. En fait, car ils ( sans même dicter comment gérer directement la mémoire) ont réussi à définir l'encapsulation de ce stockage de longue durée et les règles pour savoir comment et quand cela peut être créé et détruit.
C'est la même chose pour Ab2
mais la façon dont ils le présentent me donne l'impression que c'est très différent de Ab1
. Et en effet, cela s'avère être. Ils utilisent une pile pour la manipulation d'état de Ab2
et réserver la mémoire du tas pour Ab1
. Ab2
meurt après un certain temps (après avoir terminé l'exécution).
De plus, la façon dont vous définissez quoi faire avec Ab2
se fait via une autre partie de stockage appelée Ab2_Code
et spécification pour Ab1
implique de même Ab1_Code
Je dirais que c'est fantastique! J'obtiens tellement de commodité qui me permet de résoudre tant de problèmes.
Maintenant, je regarde toujours du point de vue d'un profane, donc je ne me sens pas vraiment surpris d'avoir traversé le processus de réflexion, mais si vous remettez en question les choses de haut en bas, les choses peuvent devenir un peu difficiles à mettre en perspective. (Je pense que c'est ce qui s'est passé dans votre cas)
BTW, j'ai oublié de mentionner que Ab1
est appelé un objet officiellement et Ab2
a pile de fonctions tandis que Ab1_Code
est la définition de classe et Ab2_Code
est le code définition de la fonction.
Et c'est à cause de ces différences imposées par le PL, vous trouvez qu'elles sont tellement différentes. ( votre question)
Remarque: Ne prenez pas ma représentation de Ab1
/Object
comme une longue abstraction de stockage en règle générale ou une chose concrète - c'était du point de vue du profane. Le langage de programmation offre beaucoup plus de flexibilité en termes de gestion du cycle de vie d'un objet. Ainsi, l'objet peut être déployé comme Ab1
mais cela peut être bien plus.
Et cela a-t-il une relation avec les foncteurs (objets de fonction)?
Notez que la réponse de la première partie est valable pour de nombreux langages de programmation en général (y compris C++), cette partie concerne spécifiquement C++ (dont vous avez cité les spécifications). Vous avez donc un pointeur sur une fonction, vous pouvez également avoir un pointeur sur un objet. C'est juste une autre construction de programmation définie par C++. Notez qu'il s'agit d'avoir un pointeur vers le Ab1
, Ab2
pour les manipuler plutôt que d'avoir une autre abstraction distincte sur laquelle agir.
Vous pouvez lire sur sa définition, son utilisation ici:
Permettez-moi de répondre à la question dans un langage plus simple (termes).
Il contient essentiellement des instructions pour faire quelque chose. Lors de l'exécution des instructions, la fonction peut temporairement stocker et/ou utiliser certaines données - et peut renvoyer certaines données.
Bien que les instructions soient stockées quelque part - ces instructions elles-mêmes ne sont pas considérées comme des objets.
Généralement, les objets sont des entités qui contiennent des données - qui sont manipulées/modifiées/mises à jour par des fonctions (les instructions).
Parce que les ordinateurs sont conçus de telle manière que les instructions ne dépendent pas des données.
Pour comprendre cela, pensons à une calculatrice. Nous effectuons différentes opérations mathématiques à l'aide d'une calculatrice. Disons que si nous voulons ajouter des chiffres, nous fournissons les chiffres à la calculatrice. Quels que soient les nombres, la calculatrice les ajoutera de la même manière en suivant les mêmes instructions (si le résultat dépasse la capacité de la calculatrice à stocker, il affichera une erreur - mais cela est dû à la limitation de la calculatrice à stocker le résultat (le données), non pas en raison de ses instructions d’addition).
Les ordinateurs sont conçus de la même manière. C'est pourquoi lorsque vous utilisez une fonction de bibliothèque (par exemple qsort()
) sur certaines données qui sont compatibles avec la fonction, vous obtenez le même résultat que vous attendez - et la fonctionnalité de la fonction ne change pas si les données changent - car les instructions de la fonction restent inchangées.
Les fonctions sont un ensemble d'instructions; et pendant leur exécution, certaines données temporaires peuvent être nécessaires pour le stockage. En d'autres termes, certains objets peuvent être créés temporairement lors de l'exécution de la fonction. Ces objets temporaires sont des foncteurs.