web-dev-qa-db-fra.com

Pourquoi le C n'est-il pas considéré comme un langage «orienté objet»?

Il semble que C possède ses propres quasi-objets tels que les "structs" qui peuvent être considérés comme des objets (de la manière de haut niveau que nous penserions normalement).

Et aussi, les fichiers C eux-mêmes sont fondamentalement des "modules" séparés, non? Les modules ne ressemblent-ils pas aussi à des "objets"? Je ne comprends pas pourquoi C, qui semble si similaire à C++, est considéré comme un langage "procédural" de bas niveau alors que C++ est "orienté objet" de haut niveau

* modifier: (clarification) pourquoi et où, la ligne est-elle tracée, pour ce qu'est un "objet", et ce n'est pas?

95
Dark Templar

Il semble que C possède ses propres quasi-objets tels que les "structs" qui peuvent être considérés comme des objets

Ensemble, vous et moi lisons la page Wikipedia sur la programmation orientée objet et vérifions les caractéristiques des structures de style C qui correspondent à ce qui est traditionnellement considéré comme un style orienté objet:

(OOP) est un paradigme de programmation utilisant des "objets" - des structures de données composées de champs de données et de méthodes ainsi que leurs interactions

Les structures C sont-elles composées de champs et de méthodes avec leurs interactions? Non.

Les techniques de programmation peuvent inclure des fonctionnalités telles que l'abstraction des données, l'encapsulation, la messagerie, la modularité, le polymorphisme et l'héritage.

Les structures C font-elles une de ces choses de manière "de première classe"? Non. La langue travaille contre vous à chaque étape du processus.

l'approche orientée objet encourage le programmeur à placer des données là où elles ne sont pas directement accessibles par le reste du programme

Les structures C font-elles cela? Non.

Un programme orienté objet contiendra généralement différents types d'objets, chaque type correspondant à un type particulier de données complexes à gérer ou peut-être à un objet ou un concept du monde réel

Les structures C font-elles cela? Oui.

Les objets peuvent être considérés comme enveloppant leurs données dans un ensemble de fonctions conçues pour garantir que les données sont utilisées de manière appropriée

Non.

chaque objet est capable de recevoir des messages, de traiter des données et d'envoyer des messages à d'autres objets

Une structure peut-elle envoyer et recevoir des messages? Non. Peut-il traiter des données? Non.

Les structures de données OOP ont tendance à "emporter avec elles leurs propres opérateurs"

Est-ce que cela se produit en C? Non.

Répartition dynamique ... Encapsulation ... Polymorphisme de sous-type ... Héritage d'objet ... Récursivité ouverte ... Classes d'objets ... Instances de classes ... Méthodes qui agissent sur les objets attachés ... Passage de message .. Abstraction

Y a-t-il une de ces caractéristiques des structures C? Non.

Quelles sont précisément les caractéristiques des structures qui, selon vous, sont "orientées objet"? Parce que je ne trouve pas aucune autre que le fait que les structures définissent types.

Maintenant, bien sûr, vous pouvez créer des structures qui ont des champs qui pointent vers des fonctions. Vous pouvez faire en sorte que les structures aient des champs qui sont des pointeurs vers des tableaux de pointeurs de fonction, correspondant à des tables de méthodes virtuelles. Etc. Vous pouvez bien sûr - émuler C++ en C. Mais c'est une manière très non idiomatique de programmer en C; vous feriez mieux d'utiliser simplement C++.

Et aussi, les fichiers C eux-mêmes sont essentiellement des "modules" séparés, non? Les modules ne sont-ils pas aussi un peu comme des "objets"?

Encore une fois, à quelles caractéristiques des modules pensez-vous qui les font agir comme des objets? Les modules prennent-ils en charge l'abstraction, l'encapsulation, la messagerie, la modularité, le polymorphisme et l'héritage?

L'abstraction et l'encapsulation sont assez faibles. De toute évidence, les modules sont modulaires; c'est pourquoi ils sont appelés modules. Messagerie? Ce n'est que dans le sens où un appel de méthode est un message et que les modules peuvent contenir des méthodes. Polymorphisme? Nan. Héritage? Nan. Les modules sont des candidats assez faibles pour les "objets".

104
Eric Lippert

Le mot clé est "orienté", pas "objet". Même le code C++ qui utilise des objets mais les utilise comme des structures n'est pas un objet orienté.

C et C++ peuvent tous deux faire OOP (à part aucun contrôle d'accès en C), mais la syntaxe pour le faire en C est peu pratique (pour le moins), tandis que la syntaxe en C++ le rend C est orienté vers la procédure, tandis que C++ est orienté vers les objets, malgré des capacités de base presque identiques à cet égard.

Le code qui utilise des objets pour implémenter des conceptions qui ne peuvent être effectuées qu'avec des objets (ce qui signifie généralement tirer parti du polymorphisme) est un code orienté objet. Le code qui utilise des objets aussi peu que des sacs de données, même en utilisant l'héritage dans un langage orienté objet, est vraiment juste du code procédural qui est plus compliqué qu'il ne devrait l'être. Le code en C qui utilise des pointeurs de fonction qui sont modifiés au moment de l'exécution avec des structures pleines de données fait un peu du polymorphisme et pourrait être dit "orienté objet", même dans un langage orienté sur la procédure.

46
kylben

Sur la base des principes de très haut niveau:

Un objet est une encapsulation de données et de comportements d'une manière interdépendante de sorte qu'ils fonctionnent dans leur ensemble qui peuvent être instanciés plusieurs fois et travaillés comme une boîte noire si vous connaissez l'interface externe.

Les structures contiennent des données mais aucun comportement et ne peuvent donc pas être considérées comme des objets.

Les modules contiennent à la fois le comportement et les données, mais ne sont pas encapsulés de telle manière que les deux sont liés et ne peuvent certainement pas être instanciés plusieurs fois.

Et c'est avant de passer à l'héritage et au polymorphisme ...

19
Jon Hopkins

"structs" sont uniquement des données. Le test habituel rapide et sale de "l'orientation d'objet" est: "Existe-t-il une structure qui permet d'encapsuler le code et les données comme une seule unité?". C échoue et est donc procédural. C++ réussit ce test.

6
Brian Knoblauch

C, tout comme C++, a la capacité de fournir Data Abstraction , qui est un idiome du paradigme de programmation orientée objet qui existait avant lui.

  • Les structures C peuvent avoir des données (et c'est leur objectif principal)
  • Les structures C peuvent également définir des pointeurs de fonction en tant que données
  • Les structures C peuvent et ont souvent un ensemble de fonctions qui leur sont associées, tout comme les méthodes, seul le ce pointeur n'est pas transmis implicitement, mais vous devez le spécifier explicitement comme premier argument de chaque méthode conçue pour gérer la structure spécifiée. C++ le fait automatiquement pour vous, à la fois lorsque vous définissez et appelez la classe /struct méthodes.

La POO en C++ étend les moyens aux données abstraites. Certains disent que c'est dangereux , tandis que d'autres le considèrent comme un bon outil lorsqu'il est utilisé correctement.

  • C++ rend le ce pointeur implicite en n'exigeant pas que l'utilisateur le passe dans les "méthodes de la classe/structure" tant que le type peut être (à identifiés en partie).
  • C++ vous permet de restreindre l'accès à certaines méthodes (fonctions de classe), et permet donc plus de "programmation défensive" ou "à l'épreuve des idiots".
  • C++ encourage les abstractions en offrant une sécurité de type plus forte en introduisant
    1. Le nouvel opérateur au lieu de malloc + cast
    2. Modèles au lieu de pointeurs vides
    3. Fonctions en ligne recevant des valeurs typées au lieu de macros
    4. Polymorphisme intégré que vous n'avez pas à implémenter vous-même, qui vous permet de créer des hiérarchies d'abstraction , contrats et spécialisations .

Cependant, vous trouverez de nombreux "hackers" C prêchant comment C est parfaitement capable de fournir la bonne quantité d'abstraction et comment les frais généraux créés par C++ ne font que les distraire de la résolution du problème réel.

Modèles de programmation abstraite inefficaces où, deux ans plus tard, vous remarquez qu'une certaine abstraction n'était pas très efficace, mais maintenant tout votre code dépend de tous les modèles d'objets Nice qui l'entourent, et vous ne pouvez pas le corriger sans réécrire votre application. -- Linus Torvalds

D'autres ont tendance à le considérer de manière plus équilibrée, acceptant à la fois les avantages et les inconvénients.

C, il est facile de se tirer une balle dans le pied; C++ le rend plus difficile, mais quand vous le faites, il vous fait sauter toute la jambe. - Bjarne Stroustrup

5
Yam Marcovic

Vous devez regarder l'autre côté de la médaille: C++.

Dans la POO, nous pensons à un objet abstrait (et concevons le programme en conséquence), par exemple une voiture qui peut s'arrêter, accélérer, tourner à gauche ou à droite, etc. Une structure avec un ensemble de fonctions ne correspond tout simplement pas au concept.

Avec des objets "réels", nous devons masquer les membres par exemple, ou nous pouvons également avoir un héritage avec une véritable "est une" relation et bien d'autres.

APRÈS LECTURE DES COMMENTAIRES CI-DESSOUS: Eh bien c'est vrai que (presque) tout peut être fait avec C (c'est toujours vrai), mais à première vue, je pensais que ce qui sépare c de c ++ est la façon dont vous pensez lors de la conception d'un programme.

La seule chose qui fait vraiment la différence est l'imposition de politiques par le compilateur. c'est-à-dire une fonction virtuelle pure, etc. mais cette réponse ne concernera que des problèmes techniques, mais je pense que la principale différence (comme mentionné) est la façon originale dont vous pensez pendant que vous codez, car C++ vous donne une meilleure syntaxe intégrée pour faire de telles choses, au lieu de faire OOP d'une manière un peu maladroite en C.

2
Guy L

Vous l'avez dit vous-même. Alors que C a des choses qui sont un peu comme des objets, ce ne sont toujours pas des objets, et c'est pourquoi C n'est pas considéré comme un langage OOP.

1
ryanzec

Orienté objet fait référence à la fois à un modèle architectural (ou même à un méta-modèle) et aux langages qui ont des fonctionnalités pour aider à implémenter ou à rendre obligatoire l'utilisation de ce modèle.

Vous pouvez implémenter une conception "OO" (le bureau Gnome est peut-être le meilleur exemple de OO fait en C pur), j'ai même vu cela fait avec COBOL!

Cependant, être capable d'implémenter une dose de conception OO ne rend pas le langage OO. Les puristes diraient que Java et C++ ne sont pas vraiment OO car vous ne pouvez pas remplacer ou hériter les "types" de base tels que "int" et "char", et, Java ne prend pas en charge l'héritage multiple. Mais comme ce sont les plus utilisés) OO langages et supporte la plupart des paradigmes les programmeurs les plus "réels" qui sont payés pour produire du code de travail les considèrent comme les OO langages).

C d'autre part ne prend en charge que les structures (comme COBOL, Pascal et des dizaines d'autres langages procéduraux), vous pourriez faire valoir qu'il prend en charge l'héritage multiple en ce que vous pouvez utiliser n'importe quelle fonction sur n'importe quel élément de données, mais la plupart considèrent cela comme un bogue plutôt qu'une fonctionnalité.

1
James Anderson

Je pense que C est parfaitement bien et décent pour implémenter des concepts orientés objet, haussement d'épaules. La plupart des différences que je vois entre le sous-ensemble dénominateur commun des langues considérées comme orientées objet sont mineures et de nature syntaxique de mon genre de point de vue pragmatique.

Commençons par, disons, cacher des informations. En C, nous pouvons y parvenir en masquant simplement la définition d'une structure et en travaillant avec elle à l'aide de pointeurs opaques. Cela modélise efficacement la distinction public vs private des champs de données que nous obtenons avec les classes. Et c'est assez facile à faire et à peine anti-idiomatique, car la bibliothèque C standard s'appuie fortement sur cela pour obtenir le masquage des informations.

Bien sûr, vous perdez la possibilité de contrôler facilement où la structure est allouée en mémoire en utilisant des types opaques, mais ce n'est qu'une différence notable entre, disons, C et C++. C++ est certainement un outil supérieur lorsque l'on compare sa capacité à programmer des concepts orientés objet sur C tout en conservant le contrôle des dispositions de la mémoire, mais cela ne signifie pas nécessairement que Java ou C # est supérieur à C à cet égard, puisque ces deux vous font complètement perdre la capacité de contrôler où les objets sont alloués en mémoire.

Et nous devons utiliser une syntaxe comme fopen(file, ...); fclose(file); par opposition à file.open(...); file.close(); mais grand whoop. Qui s'en soucie vraiment? Peut-être juste quelqu'un qui s'appuie fortement sur l'auto-complétion dans leur IDE. J'admets que cela peut être une fonctionnalité très utile d'un point de vue pratique, mais peut-être pas une qui nécessite une discussion sur la pertinence d'une langue pour la POO.

Nous n'avons pas la capacité d'implémenter efficacement les champs protected. Je vais me soumettre totalement là-bas. Mais je ne pense pas qu'il existe une règle concrète qui dit: "Toutes les langues OO devraient avoir une fonctionnalité pour permettre aux sous-classes d'accéder aux membres d'une classe de base qui ne devraient toujours pas être accessibles) par les clients normaux. "En outre, je vois rarement des cas d'utilisation pour les membres protégés qui ne sont pas au moins un peu méfiants de devenir un obstacle à la maintenance.

Et bien sûr, nous devons "émuler" OO polymorphisme avec des tables de pointeurs de fonction et des pointeurs vers eux pour une répartition dynamique avec un peu plus de passe-partout pour initialiser ces analogiques vtables et vptrs, mais un peu de passe-partout ne m'a jamais causé beaucoup de peine.

L'héritage est à peu près la même chose. Nous pouvons facilement modéliser cela à travers la composition, et au fonctionnement interne des compilateurs, cela se résume à la même chose. Bien sûr, nous perdons la sécurité de type si nous voulons downcast, et là je dirais que si vous voulez être downcasting du tout, s'il vous plaît ne pas utiliser C pour cela parce que le les choses que les gens font en C pour émuler downcasting peuvent être horribles du point de vue de la sécurité, mais je préfère que les gens ne le fassent pas downcasting du tout. La sécurité de type est quelque chose que vous pouvez facilement commencer à manquer en C car le compilateur offre tellement de latitude pour interpréter les choses comme de simples bits et octets, sacrifiant la capacité de détecter les erreurs potentielles au moment de la compilation, mais certains langages considérés comme orientés objet ne sont pas 't même tapé statiquement.

Donc non, je pense que ça va. Bien sûr, je n'utiliserais pas C pour essayer de créer une base de code à grande échelle conforme aux principes SOLID, mais ce n'est pas nécessairement en raison de ses défauts sur le front orienté objet. beaucoup de fonctionnalités qui me manqueraient si j'essayais d'utiliser C à cette fin seraient liées à des fonctionnalités de langage qui ne sont pas directement considérées comme une condition préalable pour OOP comme la sécurité de type fort, les destructeurs qui sont automatiquement invoqués lorsque les objets sortent de la portée, la surcharge de l'opérateur, les modèles/génériques et la gestion des exceptions. C'est quand je manque ces fonctionnalités auxiliaires que j'atteins pour C++.

0
user204677

Regardons simplement la définition de OO:

  • Messagerie
  • Encapsulation
  • Reliure tardive

C ne fournit aucun de ces trois. En particulier, il ne fournit pas Messaging, qui est le plus important.

0
Jörg W Mittag

Il existe un certain nombre d'ingrédients clés dans l'OO, mais les plus importants sont que la majorité du code ne sait pas ce qui se trouve à l'intérieur d'un objet (ils voient l'interface de surface, pas l'implémentation), que l'état d'un objet est une unité gérée (c.-à-d. lorsque l'objet cesse d'être, son état le fait également), et lorsqu'un code appelle une opération sur un objet, ils le font sans savoir exactement ce que cette opération est ou implique (tout ce qu'ils font, c'est suivre un modèle pour lancer un "Message" sur le mur).

C encapsule très bien; le code qui ne voit pas la définition d'une structure ne peut pas (légitimement) y jeter un œil. Tout ce que vous devez faire est de mettre une définition comme celle-ci dans un fichier d'en-tête:

struct FooImpl;
typedef struct FooImpl *Foo;

Bien sûr, il y aura un besoin pour une fonction qui construit Foos (ie, une fabrique) et qui devrait déléguer une partie du travail à l'objet alloué lui-même (ie, via une méthode "constructeur"), et aussi d'avoir un moyen de disposer à nouveau de l'objet (tout en le laissant nettoyer via sa méthode "destructrice") mais ce sont des détails.

La répartition des méthodes (c'est-à-dire la messagerie) peut également être effectuée par la convention selon laquelle le premier élément de la structure est en fait un pointeur vers une structure pleine de pointeurs de fonction, et que chacun de ces pointeurs de fonction doit prendre un Foo comme son premier argument. Dispatch consiste alors à rechercher la fonction et à l'appeler avec la bonne réécriture d'arguments, ce qui n'est pas si difficile à faire avec un macro et un peu rusé. (Cette table de fonctions est au cœur de ce qu'est réellement une classe dans un langage comme C++.)

De plus, cela vous donne également une liaison tardive: tout ce que le code d'expédition sait, c'est qu'il appelle un décalage particulier dans une table pointée par l'objet. Cela ne doit être défini que lors de l'allocation et de l'initialisation de l'objet. Il est possible d'aller avec des schémas de répartition encore plus complexes qui vous procurent plus de dynamisme d'exécution (au détriment de la vitesse), mais ce sont des cerises sur le mécanisme de base.

Pourtant, cela ne signifie pas que C est un langage OO. La clé est que C vous laisse faire tout le travail délicat d'écrire les conventions et le mécanisme de répartition vous-même (ou utiliser un tiers) Cela représente beaucoup de travail. Il ne fournit pas non plus de support syntaxique ou sémantique, donc l'implémentation d'un système de classe complet (avec des choses comme l'héritage) serait inutilement douloureuse; si vous avez affaire à un problème complexe qui est bien décrit par un modèle OO, un langage OO sera très utile pour écrire la solution. La complexité supplémentaire peut être justifiée).

0
Donal Fellows