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 memset
et memcpy
ne 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 memset
les 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_ptr
membre) 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:
memcpy
et memcmp
violer le système de types. Utiliser memcpy
pour copier des objets, c'est comme gagner de l'argent en utilisant un photocopieur. Utiliser memcmp
pour 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 memcpy
constitue 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 memcpy
tous 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, malloc
dans 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 free
cette mémoire. Donc , chaque fois que vous avez un fichier qui construit en C ++ avec une .cpp
extension 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,728
octets lors de sa construction en C, et également d' 9,278
octets 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.
//
commenter. Si cela fonctionne, pourquoi pas?