web-dev-qa-db-fra.com

Passer de C++ à C

Après quelques années de codage en C++, on m'a récemment proposé un travail codant en C, dans le domaine de l'intégration.

Laissant de côté la question de savoir s'il est juste ou faux de rejeter C++ dans le domaine des technologies intégrées, il y a certaines fonctionnalités/idiomes en C++ qui me manqueraient beaucoup. Juste pour en nommer quelques-uns:

  • Structures de données génériques et sécurisées par le type (à l'aide de modèles).
  • RAII. En particulier dans les fonctions avec plusieurs points de retour, par exemple ne pas avoir à se souvenir de libérer le mutex sur chaque point de retour.
  • Destructeurs en général. C'est à dire. vous écrivez une fois pour MyClass d'tor, puis si une instance de MyClass est membre de MyOtherClass, il n'est pas nécessaire que MyOtherClass désininitialise de manière explicite l'instance de MyClass: son d'tor est appelée automatiquement.
  • Espaces de noms.

Quelles sont vos expériences de passage de C++ à C?
Quels substituts C avez-vous trouvés pour vos fonctionnalités/idiomes C++ préférés? Avez-vous découvert des fonctionnalités C que vous souhaiteriez avoir avec C++?

79
george

Travaillant sur un projet intégré, j’ai essayé de travailler dans tous les C une fois, mais je ne pouvais pas le supporter. C'était tellement verbeux qu'il était difficile de lire quoi que ce soit. De plus, j'ai aimé les conteneurs optimisés pour intégrés que j'avais écrits, qui devaient se transformer en blocs #define beaucoup moins sûrs et plus difficiles à réparer.

Le code qui en C++ ressemblait à:

if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
    pktQueue.Dequeue(1);

se transforme en:

if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
    Queue_Packet_Dequeue(pktQueue, 1);

ce que beaucoup de gens diront probablement va bien, mais devient ridicule si vous devez faire plus que quelques appels de "méthode" en ligne. Deux lignes de C++ se transformeraient en cinq lignes de C (en raison des limites de longueur de ligne de 80 caractères). Les deux généreraient le même code, donc ce n’est pas comme si le processeur cible s’en occupait!

Une fois (en 1995), j'ai essayé d'écrire beaucoup de C pour un programme de traitement de données multiprocesseur. Le genre où chaque processeur a sa propre mémoire et programme. Le compilateur fourni par le fournisseur était un compilateur C (une sorte de dérivé de HighC), leurs bibliothèques étant des sources fermées, je ne pouvais pas utiliser GCC pour la construction, et leurs API étaient conçues avec l'idée que vos programmes seraient principalement les processus initialize/process./terminate variété, donc la communication entre processeurs était au mieux rudimentaire.

Environ un mois avant d'abandonner, j'ai trouvé une copie de cfront et l'ai piraté dans les fichiers makefiles afin que je puisse utiliser le langage C++. Cfront ne prenait même pas en charge les modèles, mais le code C++ était beaucoup, beaucoup plus clair.

Structures de données génériques et sécurisées par le type (utilisant des modèles).

La chose la plus proche que C a pour les modèles est de déclarer un fichier d’en-tête avec beaucoup de code qui ressemble à:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }

puis tirez-le avec quelque chose comme:

#define TYPE Packet
#include "Queue.h"
#undef TYPE

Notez que cela ne fonctionnera pas pour les types composés (par exemple, pas de files d'attente de unsigned char), sauf si vous faites d'abord un typedef.

Oh, et rappelez-vous, si ce code n'est réellement utilisé nulle part, vous ne savez même pas s'il est syntaxiquement correct.

EDIT: Encore une chose: vous aurez besoin de manuellement gérer l'instanciation du code. Si votre code "modèle" n'est pas tout fonctions inline, vous devrez alors mettre un certain contrôle pour vous assurer que les choses ne sont instanciées qu'une seule fois afin que votre éditeur de liens ne crache pas une pile de " plusieurs instances de Foo "erreurs.

Pour ce faire, vous devrez placer les éléments non-insérés dans une section "implementation" de votre fichier d'en-tête:

#ifdef implementation_##TYPE

/* Non-inlines, "static members", global definitions, etc. go here. */

#endif

Et puis, dans un _ placez dans tout votre code par variante de modèle, vous devez:

#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE

De plus, cette section d'implémentation doit être extérieur la litanie standard #ifndef/#define/#endif, car vous pouvez inclure le fichier d'en-tête de modèle dans un autre fichier d'en-tête, mais devez ensuite instancier dans un fichier .c.

Oui, ça devient vite moche. C'est pourquoi la plupart des programmeurs C n'essaient même pas.

RAII.

En particulier dans les fonctions avec plusieurs points de retour, par exemple ne pas avoir à se souvenir de libérer le mutex sur chaque point de retour.

Eh bien, oubliez votre joli code et habituez-vous à tous vos points de retour (sauf la fin de la fonction) Being gotos:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{
    TYPE * result;
    Mutex_Lock(this->lock);
    if(this->head == this->tail)
    {
        result = 0;
        goto Queue_##TYPE##_Top_exit:;
    }

    /* Figure out `result` for real, then fall through to... */

Queue_##TYPE##_Top_exit:
    Mutex_Lock(this->lock);
    return result;
}

Destructeurs en général.

C'est à dire. vous écrivez une fois pour MyClass d'tor, puis si une instance de MyClass est membre de MyOtherClass, il n'est pas nécessaire que MyOtherClass désininitialise de manière explicite l'instance de MyClass; son instance est appelée automatiquement.

La construction d'objet doit être explicitement gérée de la même manière.

Espaces de noms.

C’est en fait un problème simple à résoudre: il suffit de coller un préfixe sur le symbole tous. C’est la principale cause de la saturation de la source dont j'ai parlé plus tôt (puisque les classes sont des espaces de noms implicites). Les gens de C vivent cela depuis bien, et pour toujours, et ne verront probablement pas quel est le problème.

YMMV

64
Mike DeSimone

Je suis passé de C++ à C pour une raison différente (une sorte de réaction allergique;) et il n’ya que peu de choses qui me manquent et d’autres que j'ai gagnées. Si vous vous en tenez à C99, si vous le pouvez, il y a des constructions qui vous permettent de programmer très bien et en toute sécurité, en particulier

  • les initialiseurs désignés (éventuellement combinés avec des macros) permettent d’initialiser des classes simples en tant que indolores en tant que constructeurs
  • littéraux composés pour les variables temporaires
  • La variable for- scope peut vous aider à faire la gestion des ressources liées à la portée , en particulier pour assurer à unlock de mutexes ou free de tableaux, même dans des retours de fonction préliminaires
  • Les macros __VA_ARGS__ peuvent être utilisées pour avoir des arguments par défaut pour les fonctions et pour faire le déroulement du code 
  • inline fonctions et macros qui se combinent bien pour remplacer (en quelque sorte) les fonctions surchargées
16
Jens Gustedt

Rien de tel que le TSL n'existe pour C.
Il existe des bibliothèques offrant des fonctionnalités similaires, mais elles ne sont plus intégrées.

Pense que ce serait l’un de mes plus gros problèmes… Savoir avec quel outil je pourrais résoudre le problème, mais ne pas avoir les outils disponibles dans la langue que je dois utiliser.

8
MOnsDaR

La différence entre C et C++ réside dans la prévisibilité du comportement du code.

Il est plus facile de prédire avec une grande précision ce que votre code va faire en C, mais en C++, il pourrait devenir un peu plus difficile de faire une prédiction exacte.

La prévisibilité en C vous permet de mieux contrôler ce que fait votre code, mais cela signifie également que vous devez faire plus de choses.

En C++, vous pouvez écrire moins de code pour obtenir la même chose, mais (au moins pour moi), j'ai parfois du mal à savoir comment le code objet est mis en mémoire et son comportement attendu.

8
d.s.

Dans mon travail - qui est d'ailleurs intégré - je change constamment de C & C++.

Quand je suis en C, C++ me manque:

  • modèles (y compris, mais sans s'y limiter, les conteneurs STL). Je les utilise pour des tâches telles que des compteurs spéciaux, des pools de mémoire tampon, etc. (ma propre bibliothèque de modèles de classe et de modèles de fonction que j'utilise dans différents projets intégrés)

  • bibliothèque standard très puissante

  • les destructeurs, ce qui rend bien sûr RAII possible (mutex, désactivation des interruptions, traçage, etc.)

  • spécificateurs d'accès, pour mieux faire respecter qui peut utiliser (ne pas voir) quoi

J'utilise l'héritage sur des projets plus importants, et la prise en charge intégrée de C++ est bien plus propre et plus agréable que le "bidouillage" C qui consiste à incorporer la classe de base en tant que premier membre (sans parler de l'appel automatique de constructeurs, de listes d'initialisation, etc.). ) mais les éléments énumérés ci-dessus sont ceux qui me manquent le plus.

De plus, je ne travaille probablement que sur environ un tiers des projets C++ intégrés sur lesquels je travaille. Je me suis habitué à vivre sans eux. Je ne les manque donc pas trop lorsque je reviens en C.

D'un autre côté, lorsque je retourne à un projet C avec un nombre important de développeurs, il existe des classes entières de problèmes C++ que j'ai l'habitude d'expliquer aux personnes qui disparaissent. Principalement des problèmes dus à la complexité du C++, et aux personnes qui pensent savoir ce qui se passe, mais qui se situent vraiment dans la partie "C avec classes" de la courbe de confiance C++ .

Etant donné le choix, je préférerais utiliser C++ sur un projet, mais seulement si l'équipe est assez solide sur le langage. Bien sûr aussi en supposant que ce n’est pas un projet de 8K μC où j’écris effectivement "C" de toute façon.

7
Dan

Couple d'observations

  • Sauf si vous envisagez d'utiliser votre compilateur c ++ pour construire votre C (ce qui est possible si vous vous en tenez à un sous-ensemble bien défini de C++), vous découvrirez bientôt des choses que votre compilateur autorise en C qui pourraient être une erreur de compilation en C++.
  • Pas plus d'erreurs de modèles cryptiques (yay!)
  • Aucune programmation orientée objet (langue prise en charge)
3
hhafez

J'ai à peu près les mêmes raisons d'utiliser C++ ou un mélange de C/C++ plutôt que de C. Je peux vivre sans espaces de noms mais je les utilise tout le temps si le code standard le permet. La raison en est que vous pouvez écrire du code beaucoup plus compact en C++. C’est très utile pour moi, j’écris des serveurs en C++ qui ont tendance à planter de temps en temps. À ce stade, cela aide beaucoup si le code que vous consultez est court et composé. Par exemple, considérons le code suivant: 

uint32_t 
ScoreList::FindHighScore(
  uint32_t p_PlayerId)
{
  MutexLock lock(m_Lock); 

  uint32_t highScore = 0; 
  for(int i = 0; i < m_Players.Size(); i++)
  {
    Player& player = m_Players[i]; 
    if(player.m_Score > highScore)
      highScore = player.m_Score; 
  }

  return highScore; 
}

En C ça ressemble à: 

uint32_t 
ScoreList_getHighScore(
  ScoreList* p_ScoreList)
{
  uint32_t highScore = 0; 

  Mutex_Lock(p_ScoreList->m_Lock); 

  for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++)
  {
    Player* player = p_ScoreList->m_Players[i]; 
    if(player->m_Score > highScore)
      highScore = player->m_Score; 
  }

  Mutex_UnLock(p_ScoreList->m_Lock);

  return highScore; 
}

Pas un monde de différence. Une ligne de code supplémentaire, mais cela a tendance à s’additionner. Vous faites de votre mieux pour le garder propre et mince, mais vous devez parfois faire quelque chose de plus complexe. Et dans ces situations, vous valorisez votre nombre de lignes. Une dernière ligne est une dernière chose à examiner lorsque vous essayez de comprendre pourquoi votre réseau de diffusion cesse soudainement de transmettre des messages. 

Quoi qu'il en soit, je trouve que C++ me permet de faire des choses plus complexes de manière sécurisée. 

2
Antihero

Je pense que le principal problème pour lequel c ++ est plus difficile à accepter dans un environnement embarqué est dû au manque d'ingénieurs qui comprennent comment utiliser correctement c ++.

Oui, le même raisonnement peut être appliqué à C aussi, mais heureusement, il n'y a pas beaucoup de pièges dans C qui peuvent se tirer dans le pied. C++, en revanche, vous devez savoir quand ne pas utiliser certaines fonctionnalités de c ++.

Dans l'ensemble, j'aime bien le c ++. J'utilise cela sur la couche de services système, le pilote, le code de gestion, etc.… Mais si votre équipe n'a pas assez d'expérience avec ça, ce sera un challenge difficile.

J'ai eu l'expérience avec les deux. Lorsque le reste de l'équipe n'était pas prêt, le désastre était total. Par contre, c'était une bonne expérience.

0
KOkon