web-dev-qa-db-fra.com

Puis-je écrire du code C ++ sans en-têtes (déclarations de fonctions répétitives)?

Existe-t-il un moyen de ne pas avoir à écrire deux fois des déclarations de fonction (en-têtes) et de conserver la même évolutivité dans la compilation, la clarté dans le débogage et la flexibilité dans la conception lors de la programmation en C++?

56
thewreck

Utilisez Lzz . Il prend un seul fichier et crée automatiquement un .h et .cpp pour vous avec toutes les déclarations/définitions au bon endroit.

Lzz est vraiment très puissant et gère 99% de la syntaxe C++ complète, y compris les modèles, les spécialisations, etc., etc., etc.

Mise à jour 150120:

La nouvelle syntaxe C++ '11/14 ne peut être utilisée que dans les corps de fonction Lzz.

57
Richard Corden

J'ai ressenti la même chose quand j'ai commencé à écrire du C, alors j'ai aussi examiné cela. La réponse est que oui, c'est possible et non, vous ne voulez pas.

D'abord avec le oui.

Dans GCC, vous pouvez faire ceci:

// foo.cph

void foo();

#if __INCLUDE_LEVEL__ == 0
void foo() {
   printf("Hello World!\n");
}
#endif

Cela a l'effet escompté: vous combinez à la fois l'en-tête et la source dans un fichier qui peut être à la fois inclus et lié.

Puis avec le non:

Cela ne fonctionne que si le compilateur a accès à la source entière. Vous ne pouvez pas utiliser cette astuce lors de l'écriture d'une bibliothèque que vous souhaitez distribuer mais garder fermée. Soit vous distribuez le fichier .cph complet, soit vous devez écrire un fichier .h distinct pour accompagner votre .lib. Bien que vous puissiez peut-être le générer automatiquement avec le préprocesseur de macro. Cela deviendrait velu cependant.

Et la raison n ° 2 pourquoi vous ne voulez pas cela, et c'est probablement la meilleure: vitesse de compilation. Normalement, les fichiers sources C ne doivent être recompilés que lorsque le fichier lui-même change ou que l'un des fichiers qu'il inclut change.

  • Le fichier C peut changer fréquemment, mais la modification implique uniquement de recompiler le fichier qui a changé.
  • Les fichiers d'en-tête définissent les interfaces, ils ne devraient donc pas changer aussi souvent. Quand ils le font cependant, ils déclenchent une recompilation de chaque fichier source qui les inclut.

Lorsque tous vos fichiers sont combinés en-tête et fichiers source, chaque modification déclenchera une recompilation de tous les fichiers source. C++ n'est pas connu pour ses temps de compilation rapides, même maintenant, imaginez ce qui se passerait lorsque le projet entier devait être recompilé à chaque fois. Extrapolez ensuite cela à un projet de centaines de fichiers source avec des dépendances compliquées ...

37
rix0rrr

Désolé, mais il n'y a pas de "meilleure pratique" pour éliminer les en-têtes en C++: c'est une mauvaise idée, point final. Si vous les détestez autant, vous avez trois choix:

  • Familiarisez-vous intimement avec les composants internes C++ et les compilateurs que vous utilisez; vous allez rencontrer des problèmes différents de ceux du développeur C++ moyen, et vous devrez probablement les résoudre sans beaucoup d'aide.
  • Choisissez une langue que vous pouvez utiliser "correctement" sans être déprimé
  • Obtenez un outil pour les générer pour vous; vous aurez toujours des en-têtes, mais vous économiserez quelques efforts de frappe
27
ojrac

Dans son article Simple Support for Design by Contract in C++ , Pedro Guerreiro a déclaré:

Habituellement, une classe C++ est fournie en deux fichiers: le fichier d'en-tête et le fichier de définition. Où devrions-nous écrire les assertions: dans le fichier d'en-tête, car les assertions sont des spécifications? Ou dans le fichier de définition, car ils sont exécutables? Ou dans les deux, courir le risque d'incohérence (et de travail en double)? Nous vous recommandons plutôt d'abandonner le style traditionnel et de supprimer le fichier de définition, en utilisant uniquement le fichier d'en-tête, comme si toutes les fonctions étaient définies en ligne, un peu comme Java et Eiffel do .

Il s'agit d'un changement si radical par rapport à la normalité C++ qu'il risque de tuer l'entreprise au départ. D'un autre côté, maintenir deux fichiers pour chaque classe est si gênant, que tôt ou tard un environnement de développement C++ apparaîtra qui nous le cachera, nous permettant de nous concentrer sur nos classes, sans avoir à se soucier de l'endroit où elles sont stockées.

C'était en 2001. J'étais d'accord. Nous sommes en 2009 et aucun "environnement de développement qui nous cache cela, nous permettant de nous concentrer sur nos cours" n'est apparu. Au lieu de cela, de longs temps de compilation sont la norme.


Remarque: Le lien ci-dessus semble être mort maintenant. Il s'agit de la référence complète à la publication, telle qu'elle apparaît dans la section Publications du site Web de l'auteur:

Pedro Guerreiro, Simple Support for Design by Contract in C++, TOOLS USA 2001, Actes, pages 24-34, IEEE, 2001.

10
Daniel Daranas

Il n'existe aucun moyen pratique de contourner les en-têtes. La seule chose que vous pourriez faire est de mettre tout le code dans un gros fichier c ++. Cela se terminera dans un gâchis irréalisable, alors ne le faites pas.

Pour le moment, les fichiers d'en-tête C++ sont un mal nécessaire. Je ne les aime pas, mais il n'y a aucun moyen de les contourner. J'adorerais voir quelques améliorations et de nouvelles idées sur le problème.

Btw - une fois que vous vous y êtes habitué, ce n'est plus que mauvais .. C++ (et tout autre langage aussi) a des choses plus ennuyeuses.

8
Nils Pipenbrinck

Ce que j'ai vu des gens comme vous, c'est écrivez tout dans les en-têtes . Cela vous donne la propriété souhaitée de ne devoir écrire les profils de méthode qu'une seule fois.

Personnellement, je pense qu'il y a de très bonnes raisons pour lesquelles il vaut mieux séparer la déclaration et la définition, mais si cela vous afflige, il existe un moyen de faire ce que vous voulez.

8
T.E.D.

Il existe un logiciel de génération de fichiers d'en-tête. Je ne l'ai jamais utilisé, mais cela pourrait valoir la peine d'être étudié. Par exemple, consultez mkhdr! Il analyse soi-disant les fichiers C et C++ et génère les fichiers d'en-tête appropriés.

(Cependant, comme le souligne Richard, cela semble vous empêcher d'utiliser certaines fonctionnalités C++. Voir la réponse de Richard à la place ici juste dans ce fil .)

5
0scar

Vous devez écrire la fonction declaration deux fois, en fait (une fois dans le fichier d'en-tête, une fois dans le fichier d'implémentation). La définition (implémentation AKA) de la fonction sera écrite une seule fois, dans le fichier d'implémentation.

Vous pouvez écrire tout le code dans les fichiers d'en-tête (c'est en fait une pratique très utilisée dans la programmation générique en C++), mais cela implique que chaque fichier C/CPP incluant cet en-tête impliquera une recompilation de l'implémentation à partir de ces fichiers d'en-tête.

Si vous pensez à un système similaire à C # ou Java, ce n'est pas possible en C++.

5
Cătălin Pitiș

Personne n'a encore mentionné Visual-Assist X dans Visual Studio 2012.

Il a un tas de menus et de raccourcis clavier que vous pouvez utiliser pour soulager la peine de maintenir les en-têtes:

  • "Créer une déclaration" copie la déclaration de fonction de la fonction actuelle dans le fichier .hpp.
  • "Refactor..Change signature" vous permet de mettre à jour simultanément les fichiers .cpp et .h avec une seule commande.
  • Alt-O vous permet de basculer instantanément entre les fichiers .cpp et .h.
3
Contango

En fait ... Vous pouvez écrire l'implémentation entière dans un fichier. Les classes modèles sont toutes définies dans le fichier d'en-tête sans fichier cpp.

Vous pouvez également enregistrer ensuite avec les extensions que vous souhaitez. Ensuite, dans les instructions #include, vous incluriez votre fichier.

/* mycode.cpp */
#pragma once
#include <iostreams.h>

class myclass {
public:
  myclass();

  dothing();
};

myclass::myclass() { }
myclass::dothing()
{
  // code
}

Puis dans un autre fichier

/* myothercode.cpp */
#pragma once
#include "mycode.cpp"

int main() {
   myclass A;
   A.dothing();
   return 0;
}

Vous devrez peut-être configurer certaines règles de construction, mais cela devrait fonctionner.

3
Kieveli

Je comprends tes problèmes. Je dirais que le problème principal de C++ est la méthode de compilation/construction héritée du C. La structure d'en-tête C/C++ a été conçue à une époque où le codage impliquait moins de définitions et plus d'implémentations. Ne jetez pas de bouteilles sur moi, mais ça ressemble à ça.

Depuis lors, le OOP a conquis le monde et le monde est plus sur les définitions que sur les implémentations. En conséquence, y compris les en-têtes rend assez difficile de travailler avec un langage où les collections fondamentales telles que celles de la STL faite avec des modèles qui sont notoirement difficiles à gérer pour le compilateur. Toute cette magie avec les en-têtes précompilés n'aide pas tellement quand il s'agit de TDD, d'outils de refactoring, de l'environnement de développement général.

Bien sûr, les programmeurs C ne souffrent pas trop de cela car ils n'ont pas de fichiers d'en-tête lourds de compilation et ils sont donc satisfaits de la chaîne d'outils de compilation assez simple et de bas niveau. Avec C++, c'est une histoire de souffrance: déclarations en avant sans fin, en-têtes précompilés, analyseurs externes, préprocesseurs personnalisés, etc.

Beaucoup de gens, cependant, ne réalisent pas que le C++ est le SEUL langage qui a des solutions fortes et modernes pour les problèmes de haut et de bas niveau. Il est facile de dire que vous devriez opter pour une autre langue avec un système de réflexion et de construction approprié, mais cela n'a pas de sens que nous devons sacrifier les solutions de programmation de bas niveau avec cela et nous devons compliquer les choses avec un langage de bas niveau mélangé avec une solution basée sur une machine virtuelle/JIT.

J'ai cette idée depuis un certain temps maintenant, que ce serait la chose la plus cool sur terre d'avoir une chaîne d'outils c ++ basée sur des "unités", similaire à celle de D. Le problème vient de la partie multiplateforme: l'objet les fichiers sont capables de stocker n'importe quelle information, pas de problème avec cela, mais comme sur Windows la structure du fichier objet est différente de celle de l'ELF, il serait difficile d'implémenter une solution multiplateforme pour stocker et traiter à mi-chemin -unités de compilation.

2
progician

Vous pouvez éviter les en-têtes. Complètement. Mais je ne le recommande pas.

Vous serez confronté à des limitations très spécifiques. L'un d'eux est que vous ne pourrez pas avoir de références circulaires (vous ne pourrez pas faire en sorte que la classe Parent contienne un pointeur vers une instance de la classe ChildNode, et la classe ChildNode contient également un pointeur vers une instance de la classe Parent. ça devrait être l'un ou l'autre.)

Il y a d'autres limitations qui finissent par rendre votre code vraiment bizarre. Respectez les en-têtes. Vous apprendrez à vraiment les aimer (car ils fournissent un joli résumé rapide de ce qu'une classe peut faire).

2
bobobobo

Après avoir lu toutes les autres réponses, je constate qu'il manque un travail en cours pour ajouter la prise en charge des modules dans la norme C++. Il ne parviendra pas à C++ 0x, mais l'intention est qu'il sera abordé dans une révision technique ultérieure (plutôt que d'attendre une nouvelle norme, qui prendra du temps).

La proposition qui était en discussion est N207 .

La mauvaise partie est que vous n'obtiendrez pas cela, pas même avec les derniers compilateurs c ++ 0x. Vous devrez attendre. En attendant, vous devrez faire un compromis entre l'unicité des définitions dans les bibliothèques en-tête uniquement et le coût de la compilation.

Pour proposer une variante de la réponse populaire de rix0rrr:

// foo.cph

#define INCLUDEMODE
#include "foo.cph"
#include "other.cph"
#undef INCLUDEMODE

void foo()
#if !defined(INCLUDEMODE)
{
   printf("Hello World!\n");
}
#else
;
#endif

void bar()
#if !defined(INCLUDEMODE)
{
    foo();
}
#else
;
#endif

Je ne le recommande pas, mais je pense que cette construction démontre la suppression de la répétition de contenu au détriment de la répétition par cœur. Je suppose que cela facilite les copier-pâtes? Ce n'est pas vraiment une vertu.

Comme pour toutes les autres astuces de cette nature, une modification du corps d'une fonction nécessitera toujours une recompilation de tous les fichiers, y compris le fichier contenant cette fonction. Des outils automatisés très prudents peuvent partiellement éviter cela, mais ils devraient toujours analyser le fichier source pour vérifier et être soigneusement construits pour ne pas réécrire leur sortie si ce n'est pas différent.

Pour les autres lecteurs: J'ai passé quelques minutes à essayer de comprendre les gardes dans ce format, mais je n'ai rien trouvé de bon. Commentaires?

2
Phil Miller

Il est tout à fait possible de développer sans fichiers d'en-tête. On peut inclure un fichier source directement:

#include "MyModule.c"

Le problème majeur avec ceci est celui des dépendances circulaires (ie: en C vous devez déclarer une fonction avant de l'appeler). Ce n'est pas un problème si vous concevez votre code complètement de haut en bas, mais cela peut prendre un certain temps pour envelopper la tête autour de ce type de modèle de conception si vous n'êtes pas habitué.

Si vous devez absolument avoir des dépendances circulaires, on peut envisager de créer un fichier spécifiquement pour les déclarations et de l'inclure avant tout le reste. C'est un peu gênant, mais toujours moins polluant que d'avoir un en-tête pour chaque fichier C.

Je développe actuellement cette méthode pour l'un de mes grands projets. Voici une liste des avantages que j'ai connus:

  • Beaucoup moins de pollution de fichiers dans votre arbre source.
  • Temps de construction plus rapides. (Un seul fichier objet est produit par le compilateur, main.o)
  • Créez des fichiers plus simplement. (Un seul fichier objet est produit par le compilateur, main.o)
  • Pas besoin de "nettoyer". Chaque build est "propre".
  • Moins de code de plaque de chaudière. Moins de code = moins de bugs potentiels.

J'ai découvert que Gish (un jeu de Cryptic Sea, Edmund McMillen) utilisait une variation de cette technique dans son propre code source.

1
Nathaniel Sabanski

Pour autant que je sache, non. Les en-têtes font partie intégrante de C++ en tant que langage. N'oubliez pas que la déclaration directe permet au compilateur d'inclure simplement un pointeur de fonction sur un objet/fonction compilé sans avoir à inclure la fonction entière (que vous pouvez contourner en déclarant une fonction en ligne (si le compilateur en a envie).

Si vous détestez vraiment, vraiment, vraiment créer des en-têtes, écrivez plutôt un script Perl pour les générer automatiquement. Je ne suis pas sûr de le recommander.

1
mikek

Vous pouvez vous passer des en-têtes. Mais pourquoi consacrer des efforts à éviter les bonnes pratiques soigneusement élaborées qui ont été développées au fil des ans par des experts.

Quand j'écrivais de base, j'aimais beaucoup les numéros de ligne. Mais, je ne penserais pas à essayer de les brouiller en C++, car ce n'est pas la façon C++. Il en va de même pour les en-têtes ... et je suis sûr que d'autres réponses expliquent tout le raisonnement.

0
Scott Langham

Pour pratique non, ce n'est pas possible. Techniquement, oui, vous le pouvez. Mais, franchement, c'est un abus de la langue, et vous devez vous adapter à la langue. Ou passez à quelque chose comme C #.

0
Paul Nathan

Il est recommandé d'utiliser les fichiers d'en-tête, et après un certain temps, ils se transformeront en vous. Je suis d'accord qu'il est plus facile de n'avoir qu'un seul fichier, mais cela peut également entraîner un mauvais codage.

certaines de ces choses, même si elles vous semblent gênantes, vous permettent d'en obtenir plus que ce qui vous semble.

par exemple, pensez aux pointeurs, passez les paramètres par valeur/par référence ... etc.

pour moi les fichiers d'en-tête me permettent de bien structurer mes projets

0
Katanagashi

Apprenez à reconnaître que les fichiers d'en-tête sont une bonne chose. Ils séparent la façon dont les codes apparaissent pour un autre utilisateur de la mise en œuvre de la façon dont il effectue réellement ses opérations.

Lorsque j'utilise le code de quelqu'un, je veux maintenant avoir à parcourir toute l'implémentation pour voir quelles sont les méthodes d'une classe. Je me soucie de ce que fait le code, pas de la façon dont il le fait.

0
Richard

Vous pouvez soigneusement disposer vos fonctions afin que toutes les fonctions dépendantes soient compilées après leurs dépendances, mais comme Nils l'a laissé entendre, ce n'est pas pratique.

Catalin (pardonnez les signes diacritiques manquants) a également suggéré une alternative plus pratique de définir vos méthodes dans les fichiers d'en-tête. Cela peut réellement fonctionner dans la plupart des cas .. surtout si vous avez des gardes dans vos fichiers d'en-tête pour vous assurer qu'ils ne sont inclus qu'une seule fois.

Personnellement, je pense que les fichiers d'en-tête + les fonctions de déclaration sont beaucoup plus souhaitables pour obtenir un nouveau code, mais c'est une préférence personnelle, je suppose ...

0
John Weldon

Cela a été "relancé" grâce à un doublon ...

Dans tous les cas, le concept d'un en-tête est valable, c'est-à-dire séparer l'interface du détail de la mise en œuvre. L'en-tête décrit comment vous utilisez une classe/méthode, et non comment elle le fait.

L'inconvénient est le détail dans les en-têtes et toutes les solutions de contournement nécessaires. Ce sont les principaux problèmes tels que je les vois:

  • génération de dépendances. Lorsqu'un en-tête est modifié, tout fichier source qui inclut cet en-tête nécessite une recompilation. Le problème est bien sûr de savoir quels fichiers sources l'utilisent réellement. Lorsqu'une génération "propre" est effectuée, il est souvent nécessaire de mettre en cache les informations dans une sorte d'arbre de dépendance pour plus tard.

  • inclure des gardes. D'accord, nous savons tous comment les écrire, mais dans un système parfait, cela ne serait pas nécessaire.

  • détails privés. Dans une classe, vous devez mettre les détails privés dans l'en-tête. Oui, le compilateur a besoin de connaître la "taille" de la classe, mais dans un système parfait, il pourrait le lier dans une phase ultérieure. Cela conduit à toutes sortes de solutions de contournement comme pImpl et à l'utilisation de classes de base abstraites même lorsque vous n'avez qu'une seule implémentation simplement parce que vous souhaitez masquer une dépendance.

Le système parfait fonctionnerait avec

  • définition et déclaration de classe distinctes
  • Une liaison claire entre ces deux afin que le compilateur sache où se trouve une déclaration de classe et sa définition, et sache quelle est la taille d'une classe.
  • Vous déclarez using class plutôt que pré-processeur #include. Le compilateur sait où trouver une classe. Une fois que vous avez fait "utiliser la classe", vous pouvez utiliser ce nom de classe sans le qualifier.

Je serais intéressé de savoir comment D le fait.

Quant à savoir si vous pouvez utiliser C++ sans en-têtes, je dirais que non, vous en avez besoin pour les classes de base abstraites et la bibliothèque standard. Mis à part cela, vous pourriez vous en passer sans eux, même si vous ne le voudriez probablement pas.

0
CashCow