Est-ce une mauvaise pratique d'utiliser un compilateur C ++ uniquement pour surcharger une fonction?


60

Je travaille donc sur une conception de logiciel utilisant C pour un processeur donné. La trousse à outils inclut la possibilité de compiler C ainsi que C ++. Pour ce que je fais, il n'y a pas d'allocation de mémoire dynamique disponible dans cet environnement et le programme est globalement assez simple. Sans parler du fait que le périphérique n'a presque pas de puissance de processeur ni de ressources. Il n'est vraiment pas nécessaire d'utiliser le C ++.

Cela étant dit, il y a quelques endroits où je surcharges (une fonctionnalité de C ++). Je dois envoyer quelques types de données différents et je n'ai pas envie d'utiliser le printfformatage de style avec une sorte d' %sargument (ou autre). J'ai vu des personnes qui n'avaient pas accès à un compilateur C ++ printf, mais dans mon cas, le support C ++ est disponible.

Maintenant, je suis sûr que je pourrais avoir la question de savoir pourquoi je dois commencer par surcharger une fonction. Je vais donc essayer de répondre à cela maintenant. J'ai besoin de transmettre différents types de données via un port série. J'ai donc quelques surcharges qui transmettent les types de données suivants:

unsigned char*
const char*
unsigned char
const char

Je préférerais simplement ne pas avoir une méthode qui gère toutes ces choses. Quand je demande à la fonction que je veux juste de transmettre le port série, je n'ai pas beaucoup de ressources , donc je ne veux pas faire à peine RIEN mais ma transmission.

Quelqu'un d'autre a vu mon programme et m'a demandé: "Pourquoi utilisez-vous les fichiers du RPC?" Donc, c'est ma seule raison. Est-ce une mauvaise pratique?

Mise à jour

J'aimerais répondre à quelques questions posées:

Une réponse objective à votre dilemme dépendra de:

  1. Si la taille de l'exécutable augmente considérablement si vous utilisez C ++.

À l'heure actuelle, la taille de l'exécutable consomme 4,0% de la mémoire du programme (sur 5248 octets) et 8,3% de la mémoire de données (sur 342 octets). Autrement dit, compiler pour C ++ ... Je ne sais pas à quoi cela ressemblerait pour C parce que je n’utilisais pas le compilateur C. Je sais que ce programme ne se développera plus, alors pour ce qui est des ressources limitées, je dirais que ça va aller là-bas ...

  1. Indique s'il y a un impact négatif notable sur les performances si vous utilisez C ++.

Eh bien, s’il y en a, je n’ai rien remarqué ...

  1. Si le code peut être réutilisé sur une plate-forme différente où seul un compilateur C est disponible.

Je sais que la réponse à cette question est définitivement non . Nous envisageons actuellement de passer à un processeur différent, mais uniquement à des processeurs plus puissants basés sur ARM (tous, je le sais, possèdent des chaînes d'outils du compilateur C ++).


59
On m'a appris à utiliser C ++ pour un projet en n'utilisant que des fonctionnalités C juste pour pouvoir //commenter. Si cela fonctionne, pourquoi pas?
Jules

76
La mauvaise pratique serait de vous limiter à C lorsque vous utilisez bien les fonctionnalités qu’il ne fournit pas.
Jerry Coffin

35
Lorsque vous dites "utiliser un compilateur C ++", vous voulez dire "utiliser C ++". Dis le juste. Vous ne pouvez pas compiler C avec un compilateur C ++, mais vous pouvez passer facilement du C au C ++, comme vous le feriez réellement.
user253751

4
"Pour ce que je fais, il n'y a pas d'allocation de mémoire dynamique sur le processeur et le programme est globalement assez simple. Sans parler du fait que le périphérique n'a presque pas de puissance de processeur ni de ressources. Il n'y a vraiment aucune nécessité d'utiliser un C ++, que ce soit." J'espère que la première de ces deux phrases est censée être une raison de ne pas utiliser le C ++, car elles sont plutôt mauvaises si elles le sont. C ++ convient parfaitement aux systèmes embarqués.
Pharap

21
@Jules Je suis sûr que vous le savez et que vous y avez réfléchi un moment, mais au cas où quelqu'un le lirait pas: les //commentaires sont dans la norme C depuis C99.
Davislor

Réponses:


77

Je n'irais pas jusqu'à qualifier cela de "mauvaise pratique" en soi , mais je ne suis pas non plus convaincu que ce soit vraiment la bonne solution à votre problème. Si vous ne voulez que quatre fonctions distinctes pour faire vos quatre types de données, pourquoi ne pas ce que les programmeurs C ont fait depuis des temps immémoriaux:

void transmit_uchar_buffer(unsigned char *buffer);
void transmit_char_buffer(char *buffer);
void transmit_uchar(unsigned char c);
void transmit_char(char c);

C’est effectivement ce que le compilateur C ++ fait en coulisse de toute façon et ce n’est pas une lourde charge pour le programmeur. Évite tous les problèmes de "pourquoi n'écris-tu pas assez C avec un compilateur C ++", et signifie que personne d'autre sur ton projet ne va confondre quels bits de C ++ sont "autorisés" et quels bits ne le sont pas.


8
Je dirais pourquoi pas parce que le type transmis est (probablement) un détail d'implémentation, donc le cacher et laisser le compilateur s'occuper de la sélection de l'implémentation peut donner un code plus lisible. Et si l'utilisation d'une fonctionnalité C ++ améliore la lisibilité, pourquoi ne pas le faire?
Jules

29
On peut même #definetransmettre () en utilisant C11_Generic
Deduplicator

16
@Jules Parce qu'il est alors très difficile de savoir quelles fonctionnalités C ++ sont autorisées à être utilisées dans le projet. Une modification potentielle contenant des objets serait-elle rejetée? Qu'en est-il des modèles? Commentaires de style C ++? Bien sûr, vous pouvez contourner ce problème avec un document de style de codage, mais si vous ne faites qu'un simple cas de surcharge de fonctions, écrivez simplement C à la place.
Philip Kendall

25
@Phillip "Les commentaires de style C ++" sont valables en C depuis plus de dix ans.
David Conrad

12
@Jules: alors que le type transmis est probablement un détail d'implémentation pour un logiciel qui crée des devis d'assurance, l'application OP semble être un système intégré effectuant une communication série, où le type et la taille de données revêtent une importance considérable.
Whatsisname

55

Utiliser seulement certaines fonctionnalités de C ++ tout en le traitant comme C n'est pas vraiment commun, mais ce n'est pas non plus inconnu. En fait, certaines personnes n'utilisent même aucune fonctionnalité du C ++, à l'exception de la vérification de type plus stricte et plus puissante. Ils écrivent simplement C (en prenant soin de n'écrire que dans l'intersection commune de C ++ et C), puis compilent avec un compilateur C ++ pour la vérification de type et un compilateur C pour la génération de code (ou collent simplement avec un compilateur C ++).

Linux est un exemple de base de code où les utilisateurs demandent régulièrement que des identificateurs tels classque renommés klassou kclass, afin de pouvoir compiler Linux avec un compilateur C ++. Évidemment, étant donné l'opinion de Linus sur le C ++ , ils sont toujours abattus :-D GCC est un exemple de base de code qui a d'abord été transformée en "C ++ - clean C", puis progressivement modifiée pour utiliser davantage de fonctionnalités C ++.

Il n'y a rien de mal à ce que vous faites. Si vous êtes vraiment paranoïaque à propos de la qualité de génération de code de votre compilateur C ++, vous pouvez utiliser un compilateur tel que Comeau C ++, qui compile en C comme langage cible, puis utilisez le compilateur C. De cette façon, vous pouvez également effectuer des inspections ponctuelles du code pour voir si l'utilisation de C ++ injecte un code imprévu, sensible aux performances. Cela ne devrait cependant pas être le cas pour la simple surcharge, qui génère littéralement automatiquement des fonctions nommées différemment - IOW, exactement ce que vous feriez de toute façon en C.


15

Une réponse objective à votre dilemme dépendra de:

  1. Si la taille de l'exécutable augmente considérablement si vous utilisez C ++.
  2. Indique s'il y a un impact négatif notable sur les performances si vous utilisez C ++.
  3. Si le code peut être réutilisé sur une plate-forme différente où seul un compilateur C est disponible.

Si la réponse à l'une des questions est "oui", il vaudrait peut-être mieux créer des fonctions nommées différemment pour différents types de données et rester en C.

Si la réponse à toutes les questions est "non", je ne vois aucune raison pour laquelle vous ne devriez pas utiliser le C ++.


9
Je dois dire que je n’ai jamais rencontré de compilateur C ++ qui génère un code bien pire qu’un compilateur C pour un code écrit dans le sous-ensemble partagé des deux langages. Les compilateurs C ++ ont une mauvaise réputation en termes de taille et de performances, mais j’ai constaté que c’était toujours l’utilisation inappropriée des fonctionnalités C ++ qui était à l’origine du problème ... En particulier, si vous vous souciez de la taille, n’utilisez ni iostream ni modèles, mais sinon ça devrait aller.
Jules

@Jules: Juste pour ce que ça vaut (pas grand chose, IMO), j'ai vu ce qui était vendu comme un compilateur unique pour C et C ++ (Turbo C ++ 1.0, si la mémoire sert) produisait des résultats très différents pour des entrées identiques. Si j'ai bien compris, toutefois, c'était avant qu'ils aient terminé leur propre compilateur C ++. Ainsi, même si cela ressemblait à un compilateur à l'extérieur, il disposait de deux compilateurs entièrement séparés - l'un pour le C, l'autre pour le C ++.
Jerry Coffin

1
@JerryCoffin Si vous vous en souvenez, les produits Turbo n'ont jamais eu une aussi bonne réputation. Et si c'était une version 1.0, vous pourriez être excusé pour ne pas être très raffiné. Donc, ce n'est probablement pas très représentatif.
Barmar

10
@Barmar: En fait, ils jouissaient d'une assez bonne réputation pendant assez longtemps. Leur mauvaise réputation maintenant est due principalement à leur âge. Ils sont compétitifs par rapport aux autres compilateurs du même millésime - mais personne ne pose de questions sur la façon de faire les choses avec gcc 1.4 (ou autre chose). Mais vous avez raison, ce n'est pas très représentatif.
Jerry Coffin

1
@Jules Je dirais que même les modèles vont bien. En dépit de la théorie répandue, l’instanciation d’un modèle n’augmente pas implicitement la taille du code. En général, la taille du code n’augmentera pas tant que la fonction d’un modèle n’est pas utilisée (auquel cas l’augmentation de la taille sera proportionnelle à la taille de la fonction) ou déclarer des variables statiques. Les règles d'inline s'appliquent toujours. Il est donc possible d'écrire des modèles dans lesquels toutes les fonctions sont intégrées par le compilateur.
Pharap

13

Vous posez la question comme si la compilation avec C ++ vous donnait simplement accès à plus de fonctionnalités. Ce n'est pas le cas. Compiler avec un compilateur C ++ signifie que votre code source est interprété comme une source C ++, et que C ++ est un langage différent de celui du C. Les deux ont un sous-ensemble commun suffisamment grand pour pouvoir être utilement programmé. possible d'écrire du code acceptable dans les deux langues mais interprété différemment.

Si vraiment tout ce que vous voulez, c'est une surcharge de fonctions, je ne vois vraiment pas l'intérêt de confondre le problème en y intégrant du C ++. Au lieu de fonctions différentes portant le même nom, distinguant leurs listes de paramètres, écrivez simplement des fonctions portant des noms différents.

Quant à vos critères objectifs,

  1. Si la taille de l'exécutable augmente considérablement si vous utilisez C ++.

L'exécutable peut être légèrement plus grand lorsqu'il est compilé en C ++, par rapport à un code C similaire construit en tant que tel. Au minimum, l'exécutable C ++ aura l'avantage douteux de la gestion des noms C ++ pour tous les noms de fonctions, dans la mesure où tous les symboles sont conservés dans l'exécutable. (Et c'est en fait ce qui préserve vos surcharges en premier lieu.) Il vous faudrait déterminer expérimentalement si la différence est suffisamment grande pour être importante pour vous.

  1. Indique s'il y a un impact négatif notable sur les performances si vous utilisez C ++.

Je doute que vous voyiez une différence de performance notable entre le code que vous décrivez et un analogue hypothétique au pur-C.

  1. Si le code peut être réutilisé sur une plate-forme différente où seul un compilateur C est disponible.

Je jetterai un éclairage légèrement différent à ce sujet: si vous souhaitez lier le code que vous construisez avec C ++ à un autre code, cet autre code devra également être écrit en C ++ et construit avec C ++, ou vous aurez besoin de faire une disposition spéciale dans votre propre code (déclarant une liaison "C"), ce que vous ne pouvez pas faire en plus pour vos fonctions surchargées. D'autre part, si votre code est écrit en C et compilé en C, les autres peuvent le lier avec les modules C et C ++. Ce genre de problème peut généralement être surmonté en inversant les rôles, mais comme vous ne semblez vraiment pas avoir besoin de C ++, pourquoi accepter un tel problème en premier lieu?


4
"L’exécutable peut être légèrement plus grand lorsqu’il est compilé en C ++ ... dans la mesure où des symboles sont conservés dans l’exécutable." Pour être juste, cependant, toute chaîne d'outils d'optimisation décente devrait avoir une option d'éditeur de liens pour supprimer ces symboles dans les versions "release", ce qui signifie qu'elles ne feraient que gonfler vos générations de débogage. Je ne pense pas que ce soit une perte majeure. En fait, c'est plus souvent un avantage.
Cody Grey

5

De retour dans la journée, l'utilisation d'un compilateur C ++ en tant que «meilleur C» a été promue comme cas d'utilisation. En fait, les premiers C ++ étaient exactement cela. Le principe de conception sous-jacent était que vous ne pouviez utiliser que les fonctionnalités que vous souhaitiez et que vous ne supporteriez pas le coût de fonctionnalités que vous n'utilisiez pas. Donc, vous pouvez surcharger des fonctions (en déclarant l’intention via le overloadmot clé!) Et le reste de votre projet non seulement compilerait parfaitement mais générerait un code pas pire que ce que le compilateur C produirait.

Depuis lors, les langues ont quelque peu divergé.

Chaque malloccode C sera une erreur d’appariement de types - pas un problème dans votre cas sans mémoire dynamique! De même, tous vos voidpointeurs en C vous trébucheront, car vous devrez ajouter des conversions explicites. Mais… pourquoi le faites-vous de cette façon … vous serez amené de plus en plus à utiliser les fonctionnalités C ++.

Donc, cela pourrait être possible avec du travail supplémentaire. Mais cela servira de passerelle pour l'adoption du C ++ à plus grande échelle. Dans quelques années, les gens vont se plaindre de votre code hérité qui ressemble à celui de 1989, car ils remplacent vos mallocappels par newdes blocs de code de corps de boucle pour appeler simplement un algorithme, et dénoncer le faux polymorphisme dangereux qui ont été triviales si le compilateur avait été autorisé à le faire.

D'autre part, vous savez que ce serait pareil si vous l'aviez écrit en C, est-il donc faux de l'écrire en C au lieu de C ++ étant donné son existence? Si la réponse est «non», alors utiliser des fonctionnalités choisies avec soin de C ++ ne peut pas être faux non plus .


2

De nombreux langages de programmation ont une ou plusieurs cultures qui se développent autour d'eux et ont des idées particulières sur ce qu'un langage est censé "être". Bien qu'il devrait être possible, pratique et utile, de disposer d'un langage de bas niveau adapté à la programmation système, complétant ainsi un dialecte de C adapté à cet usage, avec certaines fonctionnalités du C ++ qui conviendraient également, les cultures entourant les deux langages ne le sont pas. t particulièrement en faveur d’une telle fusion.

J'ai lu des compilateurs C incorporés intégrant certaines fonctionnalités du C ++, notamment la possibilité d'avoir

foo.someFunction(a,b,c);

interprété comme, essentiellement

typeOfFoo_someFunction(foo, a, b, c);

ainsi que la possibilité de surcharger des fonctions statiques (les problèmes de nom ne surviennent que lorsqu’ils sont exportésles fonctions sont surchargées). Il n’ya aucune raison que les compilateurs C ne soient pas en mesure de prendre en charge une telle fonctionnalité sans imposer d’exigences supplémentaires à l’environnement de liaison et d’exécution, mais le rejet réflexif de nombreux compilateurs C de la quasi-totalité du C ++ dans l’intérêt d’éviter les contraintes environnementales les provoque rejeter même les fonctionnalités qui n'imposeraient pas un tel coût. Pendant ce temps, la culture du C ++ n'a guère d'intérêt dans les environnements qui ne peuvent pas prendre en charge toutes les fonctionnalités de ce langage. Sauf si ou jusqu'à ce que la culture de C change pour accepter de telles fonctionnalités, ou la culture de C ++ pour encourager les implémentations de sous-ensembles légers, le code "hybride" est susceptible de souffrir de nombreuses limitations des deux langages tout en étant limité dans sa capacité à exploiter les avantages d'opérer dans un super ensemble combiné.

Si une plate-forme prend en charge à la fois C et C ++, le code de bibliothèque supplémentaire requis pour prendre en charge C ++ rendra probablement l'exécutable un peu plus volumineux, bien que l'effet ne soit pas particulièrement efficace. L'exécution de "fonctionnalités C" dans C ++ ne sera probablement pas particulièrement affectée. Un problème plus important pourrait être la façon dont les optimiseurs C et C ++ gèrent divers cas. Le comportement des types de données dans un C est principalement défini par le contenu des bits qui y sont stockés, et C ++ a une catégorie reconnue de structures (PODS - Plain Old Data Structures) dont la sémantique est également définie par les bits stockés. Cependant, les normes C et C ++ autorisent parfois des comportements contraires à ceux qu'impliquent les bits stockés, et les exceptions au comportement "bits stockés" diffèrent entre les deux langages.


1
Opinion intéressante mais ne répond pas à la question du PO.
R Sahu

@RSahu: Un code qui correspond à la "culture" entourant une langue est susceptible d'être mieux supporté et plus facilement adaptable à une variété d'implémentations que le code qui ne le fait pas. Les cultures de C et C ++ sont toutes les deux opposées au type d'utilisation suggéré par le PO. J'ajouterai quelques points plus spécifiques en ce qui concerne les questions spécifiques.
Supercat

1
Notez qu’il existe un groupe intégré / financier / jeux dans le comité de normes C ++ qui vise à résoudre exactement le type de situations de "petit intérêt" que vous prétendez être rejetées.
Yakk

@Yakk: J'avoue que je n'ai pas trop suivi le monde de C ++, car mes intérêts sont principalement liés à C; Je sais que des efforts ont été déployés pour créer davantage de dialectes incorporés, mais je ne pensais pas qu'ils allaient vraiment nulle part.
Supercat

2

Un autre facteur important à prendre en compte: quiconque héritera de votre code.

Est-ce que cette personne va toujours être un programmeur C avec un compilateur C? Je suppose.

Cette personne sera-t-elle également un programmeur C ++ avec un compilateur C ++? Je voudrais être raisonnablement sûr de cela avant de faire dépendre mon code de choses spécifiques à C ++.


2

Le polymorphisme est une très bonne fonctionnalité que C ++ fournit gratuitement. Cependant, vous devrez peut-être prendre en compte d'autres aspects lors du choix d'un compilateur. Il existe une alternative au polymorphisme en C mais son utilisation peut provoquer des sourcils surélevés. C'est la fonction variadique, veuillez vous référer au tutoriel de la fonction variadique .

Dans votre cas, ce serait quelque chose comme

enum serialDataTypes
{
 INT =0,
 INT_P =1,
 ....
 }Arg_type_t;
....
....
void transmit(Arg_type_t serial_data_type, ...)
{
  va_list args;
  va_start(args, serial_data_type);

  switch(serial_data_type)
  {
    case INT: 
    //Send integer
    break;

    case INT_P:
    //Send data at integer pointer
    break;
    ...
   }
 va_end(args);
}

J'aime l’approche de Phillips, mais elle jette beaucoup d’appels dans votre bibliothèque. Avec ce qui précède, l'interface est propre. Il a ses inconvénients et c'est finalement une question de choix.


Les fonctions variadiques servent principalement le cas d'utilisation où le nombre d'arguments et leurs types sont variables. Sinon, vous n'en avez pas besoin, et en particulier, c'est exagéré pour votre exemple particulier. Il serait plus simple d'utiliser une fonction ordinaire qui accepte un Arg_type_tet un void *pointant sur les données. Cela dit, oui, attribuer à la fonction (unique) un argument indiquant que le type de données est une alternative viable.
John Bollinger

1

Pour votre cas particulier de surcharge statique d'une "fonction" pour quelques types différents, vous pouvez envisager d'utiliser simplement C11 avec sa _Genericmacro- machine. Je pense que cela pourrait suffire à vos besoins limités.

En utilisant la réponse de Philip Kendall, vous pouvez définir:

#define transmit(X) _Generic((X),      \
   unsigned char*: transmit_uchar_buffer, \
   char*: transmit_char_buffer,           \
   unsigned char: transmit_uchar,         \
   char: transmit_char) ((X))

et code transmit(foo) quel que soit le type de foo(parmi les quatre types énumérés ci-dessus).

Si vous vous souciez uniquement des compilateurs GCC (et compatibles, par exemple Clang ), vous pouvez envisager __builtin_types_compatible_pson typeofextension.


1

Est-ce une mauvaise pratique d'utiliser un compilateur C ++ uniquement pour surcharger une fonction?

Mon point de vue à mon humble avis, oui, et je devrai devenir schizophrène pour pouvoir répondre à cette question, car j’aime les deux langues, mais cela n’a rien à voir avec l’efficacité, mais plutôt avec la sécurité et l’utilisation idiomatique des langues.

Côté C

D'un point de vue C, je trouve cela tellement inutile de faire en sorte que votre code nécessite C ++ uniquement pour utiliser la surcharge de fonctions. À moins que vous ne l'utilisiez pour le polymorphisme statique avec des modèles C ++, il s'agit d'un sucre syntaxique trivial acquis en échange du passage à un langage totalement différent. En outre, si vous souhaitez exporter vos fonctions vers un dylib (qu’il s’agisse ou non d’une préoccupation pratique), vous ne pouvez plus le faire de manière très pratique pour une consommation généralisée avec tous les symboles portant le nom déchiqueté.

Côté C ++

Du point de vue de C ++, vous ne devriez pas utiliser C ++ comme le C avec surcharge de fonctions. Ce n'est pas un dogmatisme stylistique, mais un dogme lié à l'utilisation pratique du C ++ quotidien.

Votre type de code C normal n’est raisonnablement sain d’esprit et «sûr» d’écrire que si vous travaillez contre le système de type C qui interdit des choses comme les copieurs structs. Une fois que vous travaillez dans une grande partie du système de type plus riche de C ++, les fonctions quotidiennes qui sont d' une valeur inestimable comme memsetet memcpyne deviennent pas des fonctions vous devez appuyer sur tout le temps. Ce sont plutôt des fonctions que vous souhaitez généralement éviter, comme la peste, car avec les types C ++, vous ne devriez pas les traiter comme des bits bruts et des octets à copier, à mélanger et à libérer. Même si votre code utilise uniquement des éléments tels que memsetles primitives et les UDT POD, à l'instant où quelqu'un ajoute un ctor à n'importe quel UDT que vous utilisez (y compris le simple ajout d'un membre qui en nécessite un, commestd::unique_ptrmembre) contre de telles fonctions ou une fonction virtuelle ou quoi que ce soit de ce genre, il rend tout votre codage C-style normal sujet à un comportement indéfini. Prenez Herb Sutter lui-même:

memcpyet memcmpvioler le système de types. Utiliser memcpypour copier des objets, c'est comme gagner de l'argent en utilisant un photocopieur. Utiliser memcmppour comparer des objets revient à comparer des léopards en comptant leurs points. Les outils et les méthodes peuvent sembler faire le travail, mais ils sont trop grossiers pour le faire de façon acceptable. Les objets C ++ concernent uniquement le masquage d'informations (sans doute le principe le plus rentable en génie logiciel; voir élément 11): les objets cachent des données (voir élément 41) et conçoivent des abstractions précises pour la copie de ces données via des constructeurs et des opérateurs d'assignation (voir éléments 52 à 55) . Bulldozer au-dessus de tout cela memcpyconstitue une violation grave de la dissimulation d'informations et conduit souvent à des fuites de mémoire et de ressources (au mieux), à des crashs (pire) ou à un comportement non défini (pire) - Normes de codage C ++.

Tant de développeurs C seraient en désaccord avec cela et à juste titre, puisque la philosophie ne s'applique que si vous écrivez du code en C ++. Vous avez probablement sont en train d' écrire un code très problématique si vous utilisez fonctionne comme memcpytous les temps dans le code qui construit que C ++ , mais il est parfaitement bien si vous le faites en C . Les deux langues sont très différentes à cet égard en raison des différences dans le système de types. Il est très tentant d'examiner le sous-ensemble de fonctionnalités que ces deux-là ont en commun et de penser que l'une peut être utilisée comme une autre, en particulier du côté C ++, mais le code C + (ou C-- code) est généralement beaucoup plus problématique que C et C. Code C ++.

De même, vous ne devriez pas utiliser, par exemple, mallocdans un contexte de style C (ce qui n’implique aucune EH) s’il peut appeler directement des fonctions C ++ pouvant émettre, car vous avez alors un point de sortie implicite dans votre fonction à la suite de la exception que vous ne pouvez pas attraper efficacement l'écriture de code de style C, avant d'être capable de freecette mémoire. Donc , chaque fois que vous avez un fichier qui construit en C ++ avec une .cppextension ou de tout et fait tout ce genre de choses comme malloc, memcpy, memset,qsort, etc., alors il demande des problèmes plus loin sur la ligne, si ce n'est déjà le cas, sauf s'il s'agit du détail d'implémentation d'une classe qui fonctionne uniquement avec des types primitifs. À ce stade, la gestion des exceptions doit toujours être protégée contre les exceptions. Si vous écrivez du code C ++ vous voulez plutôt compter généralement sur RAII et utiliser des choses comme vector, unique_ptr, shared_ptr, etc, et éviter tout codage normal style C lorsque cela est possible.

La raison pour laquelle vous pouvez jouer avec les lames de rasoir dans les types de données C et X-ray et jouer avec leurs bits et octets sans risquer de causer des dommages collatéraux dans une équipe (même si vous pouvez toujours vous blesser de toute façon) n'est pas à cause de ce que C les types peuvent faire, mais à cause de ce qu'ils ne pourront jamais faire. Dès que le système de types de C sera étendu aux fonctionnalités C ++ telles que ctors, dtors et vtables, ainsi qu’à la gestion des exceptions, tout le code C idiomatique sera rendu beaucoup plus dangereux qu’il ne l’est actuellement et vous verrez apparaître un nouveau type de code. une philosophie et une mentalité évoluant, ce qui encouragera un style de codage complètement différent, comme vous le voyez en C ++, qui considère désormais même l’utilisation d’un indicateur brut pour une classe qui gère la mémoire plutôt que, par exemple, une ressource conforme à la norme RAII unique_ptr. Cet état d'esprit ne découle pas d'un sentiment absolu de sécurité. Il découle de ce dont le C ++ a spécifiquement besoin pour être protégé contre des fonctionnalités telles que la gestion des exceptions, compte tenu de ce qu'il permet simplement via son système de types.

Sécurité d'exception

Encore une fois, dès que vous serez en C ++, les gens s'attendent à ce que votre code soit protégé contre les exceptions. Les gens pourraient maintenir votre code à l'avenir, étant donné qu'il est déjà écrit et compilé en C ++, et simplement utilisé std::vector, dynamic_cast, unique_ptr, shared_ptr, etc. dans du code appelé directement ou indirectement par votre code, le jugeant inoffensif puisque votre code est déjà "supposé" C ++. code. À ce stade, nous devons faire face à la chance que les choses vont se produire, et ensuite, lorsque vous prenez un code C parfaitement parfait et charmant, comme ceci:

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        f(n, x); // some function which, now being a C++ function, may 
                 // throw today or in the future.
        ...
        free(x);
        return success;
    }
    return fail;
}

... il est maintenant cassé. Il doit être réécrit pour être exceptionnellement sûr:

int some_func(int n, ...)
{
    int* x = calloc(n, sizeof(int));
    if (x)
    {
        try
        {
            f(n, x); // some function which, now being a C++ function, may 
                     // throw today or in the future (maybe someone used
                     // std::vector inside of it).
        }
        catch (...)
        {
            free(x);
            throw;
        }
        ...
        free(x);
        return success;
    }
    return fail;
}

Brut! C’est pourquoi la plupart des développeurs C ++ exigent plutôt ceci:

void some_func(int n, ...)
{
    vector<int> x(n);
    f(x); // some function which, now being a C++ function, may throw today
          // or in the future.
}

Le code ci-dessus est conforme à la norme RAII et est du type que les développeurs C ++ approuveraient généralement, car la fonction ne perd pas la ligne de code qui déclenche une sortie implicite à la suite d'un throw.

Choisissez une langue

Vous devez soit adopter le système de types et la philosophie de C ++ avec RAII, exception-safe, templates, OOP, etc. ou adopter C, qui tourne principalement autour de bits et d'octets bruts. Vous ne devez pas former un mariage impie entre ces deux langues, mais plutôt les séparer en langues distinctes pour les traiter de manière très différente au lieu de les brouiller.

Ces langues veulent vous épouser. En général, vous devez en choisir un au lieu de sortir ensemble et de vous amuser avec les deux. Vous pouvez aussi être un polygame comme moi et épouser les deux, mais vous devez changer complètement de pensée lorsque vous passez du temps avec l'un sur l'autre et les maintenir bien séparés les uns des autres afin qu'ils ne se combattent pas.

Taille binaire

Juste par curiosité, j'ai essayé de prendre tout à l'heure mon implémentation et mon benchmark libres et de les porter en C ++ depuis que je suis vraiment curieux de savoir ceci:

[...] Je ne sais pas à quoi ça ressemblerait pour C parce que je n'ai pas utilisé le compilateur C.

... et je voulais savoir si la taille binaire gonflerait du tout en construisant en C ++. Cela m'a obligé à saupoudrer des jets explicites un peu partout (une raison pour laquelle j'aime écrire des choses de bas niveau comme les allocateurs et les structures de données en C mieux), mais cela n'a pris qu'une minute.

Cela consistait simplement à comparer une version publiée 64 bits de MSVC pour une application de console simple et un code qui n’utilisait aucune fonctionnalité C ++, pas même une surcharge d’opérateur - juste la différence entre le construire avec C et utiliser, disons, à la <cstdlib>place de <stdlib.h>et Des choses comme ça, mais j'ai été surpris de constater que cela ne faisait aucune différence pour la taille binaire!

Le binaire était constitué d' 9,728octets lors de sa construction en C, et également d' 9,278octets lorsqu'il était compilé en tant que code C ++. En fait, je ne m'y attendais pas. Je pensais que des choses comme EH en ajouteraient au moins un peu (pensaient que ce serait au moins une centaine d'octets différents), bien qu'il ait probablement pu comprendre qu'il n'était pas nécessaire d'ajouter des instructions relatives à EH puisque je ne faisais que en utilisant la bibliothèque standard C et rien ne jette. J'ai pensé quelque choseajouterait un peu à la taille binaire de toute façon, comme RTTI. Quoi qu'il en soit, c'était plutôt cool de voir ça. Bien sûr, je ne pense pas que vous devriez généraliser à partir de ce résultat, mais au moins cela m'a un peu impressionné. Cela n'a également eu aucun impact sur les points de repère, et naturellement, puisque j'imagine que la taille binaire résultante identique signifiait également des instructions machine résultantes identiques.

Cela dit, qui se soucie de la taille binaire avec les problèmes de sécurité et d’ingénierie mentionnés ci-dessus? Encore une fois, choisissez une langue et adhérez à sa philosophie au lieu d'essayer de la bâtardiser; c'est ce que je recommande.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.