Je crois que j'ai mélangé du code C et C ++ alors que je n'aurais pas dû; Est-ce un problème et comment y remédier?

10

Contexte / scénario

J'ai commencé à écrire une application CLI uniquement en C (mon premier programme C ou C ++ approprié qui n'était pas "Hello World" ou une variante de celui-ci). Vers la mi-parcours, je travaillais avec des "chaînes" d'entrée utilisateur (tableaux de caractères) et j'ai découvert l'objet streamer de chaîne C ++. J'ai vu que je pouvais enregistrer du code en les utilisant, alors je les ai utilisés via l'application. Cela signifie que j'ai changé l'extension de fichier en .cpp et maintenant compiler l'application avec g++au lieu de gcc. Donc, sur cette base, je dirais que l'application est maintenant techniquement une application C ++ (bien que 90% + du code soit écrit dans ce que j'appellerais C, car il y a beaucoup de croisement entre les deux langues étant donné mon expérience limitée de les deux). Il s'agit d'un seul fichier .cpp d'environ 900 lignes.

Facteurs importants

Je veux que le programme soit gratuit (comme en argent) et librement distribuable et utilisable par tous. Ma préoccupation est que quelqu'un regarde le code et réfléchisse à quelque chose comme:

Oh regardez le codage, c'est affreux, ce programme ne peut pas m'aider

Quand c'est potentiellement possible! Une autre question est que le code est efficace (c'est un programme pour tester la connectivité Ethernet). Aucune partie du code ne doit être si inefficace qu'elle peut gravement nuire aux performances de l'application ou de sa sortie. Cependant, je pense que c'est une question pour Stack Overflow lorsque vous demandez de l'aide avec des fonctions, des méthodes, des appels d'objet spécifiques, etc.

Ma question

Ayant (à mon avis) mélangé C et C ++ où je ne devrais peut-être pas. Dois-je chercher à tout réécrire en C ++ (par cela, je veux dire implémenter plus d'objets et de méthodes C ++ où j'ai peut-être codé quelque chose dans un style C qui peut être condensé en utilisant des techniques C ++ plus récentes), ou supprimer l'utilisation d'objets streamer de chaîne et tout ramener "en arrière" au code C? Y a-t-il une approche correcte ici? Je suis perdu et j'ai besoin de conseils sur la façon de garder cette application «bonne» aux yeux des masses, donc ils vont l'utiliser et en bénéficier.

Le code - mise à jour

Voici un lien vers le code. Il s'agit d'environ 40% de commentaires, je commente presque toutes les lignes jusqu'à ce que je me sente plus fluide. Dans la copie à laquelle j'ai lié, j'ai supprimé à peu près tous les commentaires. J'espère que cela ne rend pas la lecture trop difficile. J'espère cependant que personne ne devrait avoir besoin de bien le comprendre. Si j'ai fait des défauts de conception fatals, j'espère qu'ils devraient être facilement identifiables. Je dois également mentionner que j'écris quelques ordinateurs de bureau et ordinateurs portables Ubuntu. Je n'ai pas l'intention de porter le code sur d'autres systèmes d'exploitation.

jwbensley
la source
3
J'ai désambiguïsé CLI pour vous. CLI peut également faire référence à Common Language Infrastructure, ce qui n'a pas beaucoup de sens du point de vue C.
Robert Harvey
Vous pouvez le réécrire en FORTRAN. Je n'ai jamais entendu parler d'OOF.
ott--
2
Je ne m'inquiéterais pas trop des gens qui n'utilisent pas votre logiciel s'il n'est pas "joli". 99% de vos utilisateurs ne regarderont même pas le code ou ne se soucieront pas de la façon dont il est écrit tant qu'il accomplit ce qu'ils ont besoin de faire. Il est cependant important que le code soit cohérent, etc. pour vous aider à le maintenir à long terme.
Evicatos
1
Pourquoi ne faites-vous pas votre logiciel gratuit (par exemple sous licence GPLv3 ), avec le code sur par exemple github . Votre code manque de LICENSEfichier. Vous pourriez obtenir des commentaires intéressants.
Basile Starynkevitch

Réponses:

13

Commençons par le début: le code mixte C et C ++ est assez courant. Vous êtes donc dans un grand club pour commencer. Nous avons d'énormes bases de code C à l'état sauvage. Mais pour des raisons évidentes, de nombreux programmeurs refusent d'écrire au moins de nouvelles choses en C, ayant accès à C ++ dans le même compilateur, de nouveaux modules commencent à être écrits de cette façon - tout d'abord en laissant les parties existantes seules.

Ensuite, certains fichiers existants sont recompilés en C ++, et certains ponts peuvent être supprimés ... Mais cela peut prendre très longtemps.

Vous êtes un peu en avance, votre système complet est maintenant en C ++, juste la plupart est écrit en "C-style". Et vous voyez que la mixité des styles pose un problème, ce que vous ne devriez pas: C ++ est un langage multi-paradigmes supportant de nombreux styles, et leur permettant de coexister pour de bon. En fait, c'est la principale force, que vous n'êtes pas obligé de choisir un seul style. Celui qui serait ici et là sous-optimal, avec un peu de chance pas partout.

Retravailler la base de code est une bonne idée, SI elle est cassée. Ou si c'est en voie de développement. Mais si cela fonctionne (dans le sens d'origine du terme), veuillez suivre le principe d'ingénierie le plus élémentaire: s'il n'est pas cassé, ne le réparez pas. Laissez les parties froides tranquilles, mettez vos efforts là où ça compte. Sur les pièces qui sont mauvaises, dangereuses - ou dans de nouvelles fonctionnalités, et juste refaçonner les pièces pour en faire un lit.

Si vous cherchez des choses générales à aborder, voici ce qui vaut la peine d'être expulsé d'une base de code C:

  • toutes les fonctions str * et char [] - les remplacer par une classe de chaîne
  • si vous utilisez sprintf, créez une version qui renvoie une chaîne avec le résultat, ou la place dans la chaîne, et remplacez usage. (Si vous ne vous êtes jamais soucié des flux, faites-vous plaisir et sautez-les, à moins que vous ne les aimiez; gcc offre une sécurité de type parfaite prête à l'emploi pour vérifier les formats, ajoutez simplement l'attribut approprié.
  • le plus malloc et gratuit - PAS avec de nouveaux et supprimer, mais vecteur, liste, carte et autres collectons.
  • le reste de la gestion de la mémoire (après les deux points précédents, cela doit être assez rare, couvrir avec des pointeurs intelligents ou implémenter vos collections spéciales
  • remplacer toute autre utilisation des ressources (FILE *, mutex, lock, etc.) pour utiliser des wrappers ou des classes RAII

Lorsque vous avez terminé, vous approchez du point où la base de code peut être raisonnablement protégée contre les exceptions, vous pouvez donc supprimer le football de code retour en utilisant des exceptions et des tentatives / captures rares dans les fonctions de haut niveau uniquement.

Au-delà de cela, il suffit d'écrire du nouveau code dans du C ++ sain, et si certaines classes naissent qui remplacent bien le code existant, prenez-les.

Je n'ai pas mentionné de choses liées à la syntaxe, utilisez évidemment des références au lieu de pointeurs dans tout le nouveau code, mais remplacer les anciennes parties C juste pour ce changement n'est pas une bonne valeur. Les conversions que vous devez adresser, éliminer tout ce que vous pouvez et utiliser des variantes C ++ dans les fonctions d'encapsulation pour le reste. Et très important, ajoutez const chaque fois que cela s'applique. Ces entrelacement avec les balles précédentes. Et consolidez vos macros et remplacez ce que vous pouvez transformer en énumération, fonction en ligne ou modèle.

Je suggère de lire les normes de codage C ++ de Sutter / Alexandrescu si ce n'est pas encore fait et de les suivre de près.

Balog Pal
la source
Merci beaucoup pour l'entrée Balog, tous les bons conseils à mes yeux et je suis d'accord avec tout cela. Je vais chercher à changer lentement la section de code par section, je pense, en priorisant le code de travail.
jwbensley
7

En bref: vous n'allez pas en enfer, il ne se passera rien de mal, mais vous ne gagnerez pas non plus de concours de beauté.

Bien qu'il soit parfaitement possible d'utiliser C ++ comme un "meilleur C", vous perdez de nombreux avantages de C ++, mais parce que vous ne vous limitez pas à la vanille C, vous n'obtenez aucun des avantages de C (simplicité, portabilité , transparence, interopérabilité). En d'autres termes: C ++ sacrifie certaines des qualités de C pour en gagner d'autres - par exemple, la transparence de C où vous pouvez toujours voir clairement où et quand les allocations de mémoire se produisent est échangée contre les abstractions plus puissantes de C ++.

Étant donné que votre code semble fonctionner correctement maintenant, ce n'est probablement pas une bonne idée de le réécrire juste pour le plaisir: conservez-le tel qu'il est pour l'instant et changez-le en C ++ plus idiomatique au fur et à mesure, une pièce à la fois, chaque fois que vous travaillez sur une pièce donnée. Et gardez à l'esprit les leçons ainsi apprises pour votre prochain projet, surtout celui-ci: C et C ++ ne sont pas le même langage, et vous feriez mieux de faire un choix à l'avance que de décider de passer au C ++ à mi-chemin de votre projet .

tdammers
la source
Merci tdammers pour le conseil. Je suis d'accord avec ce que vous dites et je le prends en compte, merci!
jwbensley
4

Le C ++ est un langage très complexe, dans le sens où il possède de nombreuses fonctionnalités. C'est également un langage qui prend en charge plusieurs paradigmes, ce qui signifie qu'il vous permet d'écrire du code entièrement procédural sans utiliser aucun objet.

Donc, parce que C ++ est si complexe, vous voyez souvent des gens utiliser un sous-ensemble limité de ses fonctionnalités dans leur code. Les débutants utilisent souvent les E / S de flux, les objets chaîne et new / delete au lieu de malloc / free, et peut-être des références au lieu de pointeurs. À mesure que vous vous familiarisez avec les fonctionnalités orientées objet, vous pouvez commencer à écrire dans un style appelé "C avec classes". Finalement, à mesure que vous en apprenez davantage sur C ++, vous commencez à utiliser des modèles, RAII , STL , des pointeurs intelligents, etc.

Le point que j'essaie de faire est que l'apprentissage du C ++ est un processus qui prend du temps. Oui, en ce moment, votre code semble avoir été écrit par un programmeur C essayant d'écrire C ++. Et puisque vous apprenez, c'est tout à fait correct. Cependant, cela me fait grincer des dents quand je vois ce genre de chose dans le code de production écrit par des programmeurs chevronnés qui devraient mieux savoir.

N'oubliez pas qu'un bon programmeur Fortran peut écrire du bon code Fortran dans n'importe quel langage. :)

Dima
la source
2

Il semble que vous récapituliez exactement le chemin emprunté par de nombreux anciens programmeurs C en allant en C ++. Vous l'utilisez comme un "meilleur C". Il y a vingt ans, cela avait un certain sens, mais à ce stade, il y a tellement de constructions plus puissantes en C ++ (comme la bibliothèque de modèles standard) que cela n'a plus de sens maintenant. Au minimum, vous devriez probablement faire ce qui suit pour éviter de donner des anévrismes aux programmeurs C ++ lorsque vous regardez votre code:

  • Utilisez des flux au lieu du? printffamille.
  • Utilisez newau lieu de malloc.
  • Utilisez les classes de conteneur STL pour toutes les structures de données.
  • Utilisez std::stringplutôt char*que possible
  • Comprendre et utiliser RAII

Si votre programme est court (et ~ 900 lignes semblent courtes), personnellement, je ne pense pas qu'essayer de créer un ensemble robuste de classes soit nécessaire ou même utile.

Gort le robot
la source
Merci pour les conseils Steven, Oui, j'ai eu la même idée sur les classes et je pense que je peux ranger le code dans un seul fichier soigné. Merci!
jwbensley
2

La réponse acceptée ne mentionne que les avantages de la conversion de C en C ++ idiomatique, comme si le code C ++ serait dans un sens absolu meilleur que le code C. Je suis d'accord avec d'autres réponses qu'il n'est probablement pas nécessaire d'apporter des modifications drastiques au code mixte s'il n'est pas cassé.

Que le mélange de C et C ++ soit durable dépend de la façon dont le mélange est effectué. Des bogues subtils peuvent être introduits, par exemple, lorsque des exceptions sont déclenchées en code C (ce qui n'est pas sûr pour les exceptions), provoquant des fuites de mémoire ou des altérations de données. La manière la plus sûre et la plus courante consiste à envelopper des bibliothèques C ou des interfaces dans des classes, ou à les utiliser dans un autre type d'isolement dans un projet C ++.

Avant de décider de réécrire tout votre projet en C ++ idiomatique (ou C), vous devez savoir que la plupart des modifications présentées dans d'autres réponses peuvent ralentir votre programme ou provoquer d'autres effets indésirables. Par exemple:

  • changer les chaînes C allouées par la pile en std :: strings peut provoquer des allocations inutiles de tas
  • la modification des pointeurs bruts vers certains types de pointeurs partagés (comme std :: shared_ptr) entraîne une surcharge d'accès en raison du comptage des références, des fonctions des membres virtuels internes et de la sécurité des threads
  • les flux de bibliothèque standard sont plus lents que leurs homologues C
  • une utilisation négligente des classes RAII, comme les conteneurs, peut entraîner des opérations inutiles, en particulier lorsque la sémantique de déplacement de C ++ 11 ne peut pas être utilisée
  • les modèles peuvent entraîner des temps de compilation plus longs, des erreurs de compilation peu claires, des problèmes de code et des problèmes de portabilité entre les compilateurs
  • il est difficile d'écrire du code sans exception, ce qui facilite l'introduction de bogues subtils pendant la conversion
  • il est plus difficile d'utiliser du code C ++ à partir d'une bibliothèque partagée que le simple C
  • C ++ n'est pas aussi largement supporté que par exemple C89

Pour conclure, la conversion d'un code C et C ++ mixte en C ++ idiomatique devrait être considérée comme un compromis qui a beaucoup plus à faire que le simple temps de mise en œuvre et l'élargissement de votre boîte à outils avec des fonctionnalités apparemment pratiques. Il est donc très difficile de donner une réponse pour le cas général autre que "ça dépend".

crafn
la source
"les flux de bibliothèque standard sont plus lents que leurs homologues C": Probablement, certainement plus difficile pour l'internationalisation que printf de toute façon.
Deduplicator
1

C'est une mauvaise forme de mélanger C et C ++. Ce sont des langues distinctes et doivent vraiment être traitées comme telles. Choisissez celui avec lequel vous êtes le plus à l'aise et essayez d'écrire du code idiomatique dans cette langue.

Si C ++ vous fait économiser beaucoup de code, restez avec C ++ et réécrivez les parties C. Je doute qu'une différence de performance soit même perceptible, si c'est ce qui vous préoccupe.

Oleksi
la source
J'ai donc cette bibliothèque que je veux utiliser qui est écrite en C, et j'ai cette autre bibliothèque que je veux utiliser qui est écrite en C ++ ... Réécrire du code qui fonctionne très bien ne fait que créer un travail inutile.
gnasher729
1

Je fais des allers-retours entre C et C ++ tout le temps et je suis le type bizarre qui préfère travailler de cette façon. Je préfère le C ++ pour les choses de haut niveau tandis que j'utilise C comme un instrument contondant qui me permet de radiographier des types de données et de les traiter comme des bits et des octets avec, disons, memcpyque je trouve pratique pour implémenter des structures de données de bas niveau et des allocateurs de mémoire . Si vous vous retrouvez à travailler au niveau des bits et octets bruts, alors le système de type très riche de C ++ n'aide en rien du tout, et je trouve souvent plus facile d'écrire un tel code en C où je peux supposer en toute sécurité que je peux traiter tout type de données C comme de simples bits et octets.

La principale chose à garder à l'esprit est le cas où ces deux langues ne s'articulent pas bien.

1. La fonction AC ne doit jamais appeler une fonction C ++ qui le peut throw. Étant donné le nombre d'emplacements que votre type quotidien de code C ++ peut implicitement exécuter dans une exception, cela signifie généralement que vos fonctions C ne devraient pas appeler des fonctions C ++ en général. Sinon, votre code C ne pourra pas libérer les ressources qu'il alloue pendant le déroulement de la pile, car il n'a aucun moyen pratique de détecter l'exception C ++. Si vous finissez par exiger que votre code C appelle une fonction C ++ en tant que rappel, par exemple, le côté C ++ doit s'assurer que toute exception qu'il rencontre est interceptée avant de revenir au code C.

2. Si vous écrivez du code C qui traite les types de données comme de simples bits et octets bruts, en passant au bulldozer sur le système de type (c'est en fait ma principale raison d'utiliser C en premier lieu), alors vous ne voulez jamais utiliser ce code contre des données C ++ types qui pourraient avoir des constructeurs et des destructeurs de copie et des pointeurs virtuels et des choses de ce genre qui doivent être respectées. Donc, généralement, ce devrait être du code C utilisant du code C de ce type, et non du code C ++ utilisant du code C de ce type.

Si vous souhaitez que votre code C ++ utilise un tel code C, vous devez généralement l'utiliser comme détail d'implémentation d'une classe qui garantit que les données qu'il stocke dans la structure de données C générique sont un type de données qui est trivial à construire et détruire. Heureusement, il existe des traits de type comme celui-ci que vous pouvez utiliser pour vérifier cela dans une assertion statique, en vous assurant que les types que vous stockez dans la structure de données C générique ont des destructeurs et des constructeurs triviaux et resteront ainsi.

Dois-je chercher à tout réécrire en C ++ (par cela, je veux dire implémenter plus d'objets et de méthodes C ++ où j'ai peut-être codé quelque chose dans un style C qui peut être condensé en utilisant des techniques C ++ plus récentes), ou supprimer l'utilisation d'objets streamer de chaîne et tout ramener "en arrière" au code C?

À mon avis, vous n'avez pas besoin de vous embêter si vous respectez les règles ci-dessus et assurez-vous que votre code est bien testé par rapport aux tests que vous avez écrits. La partie qui mérite d'être améliorée est le code que vous avez écrit en C ++ jusqu'à présent, mais cela ne signifie pas que vous devez porter le code strictement basé sur C vers C ++.

Avec le code que vous avez écrit en C ++ et compilé en code C ++, vous devez être prudent. L'utilisation d'un codage de type C en C ++ pose problème. Par exemple, si vous allouez et libérez des ressources manuellement, il est probable que votre code ne soit pas protégé contre les exceptions, car votre code peut rencontrer une exception à quel point vous quittez implicitement de la fonction avant de pouvoir utiliser freela ressource. En C ++, RAII et des choses comme les pointeurs intelligents ne sont pas une commodité moelleuse car ils peuvent apparaître si vous ne regardez que les chemins d'exécution normaux sans tenir compte des chemins exceptionnels. Ils sont souvent une nécessité fondamentale pour écrire du code correct et sûr contre les exceptions de manière simple et efficace qui ne commencera pas simplement à fuir partout lors de la rencontre d'une exception.


la source