Ich bereinige die Includes in einem C ++ - Projekt, an dem ich arbeite, und frage mich immer wieder, ob ich alle Header, die direkt in einer bestimmten Datei verwendet werden, explizit einschließen soll oder ob ich nur das Nötigste einschließen soll.
Hier ist ein Beispiel Entity.hpp
:
#include "RenderObject.hpp"
#include "Texture.hpp"
struct Entity {
Texture texture;
RenderObject render();
}
(Nehmen wir an, dass eine Forward-Deklaration für RenderObject
keine Option ist.)
Nun, ich weiß, das RenderObject.hpp
schließt ein Texture.hpp
- ich weiß das, weil jeder RenderObject
ein Texture
Mitglied hat. Ich beziehe dies jedoch ausdrücklich mit Texture.hpp
ein Entity.hpp
, da ich nicht sicher bin, ob es eine gute Idee ist, sich darauf zu verlassen, dass es in aufgenommen wird RenderObject.hpp
.
Also: Ist es eine gute Übung oder nicht?
#ifndef _RENDER_H #define _RENDER_H ... #endif
.#pragma once
es, nein?Antworten:
Sie sollten immer alle Header, die Objekte definieren, die in einer CPP-Datei verwendet werden, in diese Datei aufnehmen, unabhängig davon, was Sie über die Inhalte dieser Dateien wissen. Sie sollten in allen Header-Dateien Schutzmaßnahmen enthalten haben, um sicherzustellen, dass das mehrfache Einfügen von Headern keine Rolle spielt.
Die Gründe:
Texture
Objekte in dieser Datei handelt.RenderObject.hpp
das nicht wirklich vonTexture.hpp
selbst benötigt .Eine Konsequenz ist, dass Sie niemals einen Header in einen anderen Header einfügen sollten, es sei denn, er wird ausdrücklich in dieser Datei benötigt.
quelle
Die allgemeine Faustregel lautet: Geben Sie an, was Sie verwenden. Wenn Sie ein Objekt direkt verwenden, fügen Sie dessen Header-Datei direkt hinzu. Wenn Sie ein Objekt A verwenden, das B verwendet, aber B nicht selbst verwendet, geben Sie nur Ah an
Während wir uns mit dem Thema befassen, sollten Sie andere Header-Dateien nur dann in Ihre Header-Datei aufnehmen, wenn Sie sie tatsächlich im Header benötigen. Wenn Sie es nur in der CPP-Datei benötigen, schließen Sie es nur dort ein: Dies ist der Unterschied zwischen einer öffentlichen und einer privaten Abhängigkeit und verhindert, dass Benutzer Ihrer Klasse Header einfügen, die sie nicht wirklich benötigen.
quelle
Ja.
Sie wissen nie, wann sich diese anderen Header ändern könnten. Es ist auf der ganzen Welt sinnvoll, in jede Übersetzungseinheit die Überschriften aufzunehmen, von denen Sie wissen, dass sie von der Übersetzungseinheit benötigt werden.
Wir haben Header-Guards, um sicherzustellen, dass Doppeleinschlüsse nicht schädlich sind.
quelle
Diesbezüglich gehen die Meinungen auseinander, aber ich bin der Ansicht, dass jede Datei (ob c / cpp-Quelldatei oder h / hpp-Headerdatei) für sich kompiliert oder analysiert werden kann.
Aus diesem Grund sollten alle Dateien alle benötigten Header-Dateien enthalten. Sie sollten nicht davon ausgehen, dass bereits eine Header-Datei enthalten war.
Es ist ein echtes Problem, wenn Sie eine Header-Datei hinzufügen und feststellen müssen, dass sie ein Element verwendet, das an einer anderen Stelle definiert ist, ohne es direkt einzuschließen ... Sie müssen also suchen (und möglicherweise die falsche finden!)
Andererseits spielt es (in der Regel) keine Rolle, ob Sie eine Datei einschließen, die Sie nicht benötigen ...
Aus persönlichen Gründen ordne ich #include-Dateien in alphabetischer Reihenfolge an, teile sie in System und Anwendung auf - dies verstärkt die "in sich geschlossene und vollständig kohärente" Botschaft.
quelle
Es hängt davon ab, ob diese transitiven Inklusion durch Notwendigkeit (z. B. Basisklasse) oder aufgrund eines Implementierungsdetails (privates Mitglied) erfolgt.
Zur Verdeutlichung ist die transitive Einbeziehung beim Entfernen nur dann erforderlich, wenn zuerst die im Zwischenheader deklarierten Schnittstellen geändert wurden. Da dies bereits eine wichtige Änderung ist, muss jede CPP-Datei, die diese verwendet, trotzdem überprüft werden.
Beispiel: Ah wird von Bh eingeschlossen, das von C.cpp verwendet wird. Wenn Bh Ah für einige Implementierungsdetails verwendet, sollte C.cpp nicht davon ausgehen, dass Bh dies auch weiterhin tun wird. Wenn Bh jedoch Ah für eine Basisklasse verwendet, geht C.cpp möglicherweise davon aus, dass Bh weiterhin die relevanten Header für seine Basisklassen enthält.
Sie sehen hier den eigentlichen Vorteil, Header-Einschlüsse NICHT zu duplizieren. Sagen Sie, dass die von Bh verwendete Basisklasse wirklich nicht zu Ah gehört und in Bh selbst überarbeitet wurde. Bh ist jetzt ein eigenständiger Header. Wenn C.cpp Ah redundant enthielt, enthält es jetzt einen unnötigen Header.
quelle
Es kann einen anderen Fall geben: Sie haben Ah, Bh und Ihr C.cpp, Bh enthält Ah
In C.cpp können Sie also schreiben
Also, wenn Sie hier nicht #include "Ah" schreiben, was kann passieren? In Ihrem C.CPP werden sowohl A als auch B (z. B. Klasse) verwendet. Später haben Sie Ihren C.cpp-Code geändert, B-bezogene Inhalte entfernt und Bh dort belassen.
Wenn Sie sowohl Ah als auch Bh einschließen und zu diesem Zeitpunkt, können Tools, die unnötige Einschlüsse erkennen, Ihnen dabei helfen, darauf hinzuweisen, dass Bh-Einschlüsse nicht mehr benötigt werden. Wenn Sie nur Bh wie oben einschließen, ist es für Tools / Menschen schwierig, das unnötige Einschließen nach einer Codeänderung zu erkennen.
quelle
Ich verfolge einen etwas anderen Ansatz als die vorgeschlagenen Antworten.
Geben Sie in den Kopfzeilen immer nur ein Minimum an, genau das, was für den Kompilierungsdurchlauf erforderlich ist. Verwenden Sie nach Möglichkeit die Forward-Deklaration.
In den Quelldateien ist es nicht so wichtig, wie viel Sie einschließen. Meine Präferenzen sind immer noch mindestens, um es passieren zu lassen.
Für kleine Projekte, einschließlich Überschriften hier und da, wird es keinen Unterschied machen. Bei mittleren bis großen Projekten kann dies jedoch zu einem Problem werden. Selbst wenn die neueste Hardware zum Kompilieren verwendet wird, kann der Unterschied spürbar sein. Der Grund dafür ist, dass der Compiler den enthaltenen Header noch öffnen und analysieren muss. Um den Build zu optimieren, wenden Sie die obige Technik an (schließen Sie das Nötigste ein und verwenden Sie die Vorwärtsdeklaration).
Obwohl etwas veraltet, erklärt Large Scale C ++ Software Design (von John Lakos) dies alles im Detail.
quelle
Es empfiehlt sich, sich keine Gedanken über Ihre Header-Strategie zu machen, solange diese kompiliert wird.
Der Header-Abschnitt Ihres Codes ist nur ein Zeilenblock, den sich niemand ansehen sollte, bis ein leicht zu behebender Kompilierungsfehler auftritt. Ich verstehe den Wunsch nach 'richtigem' Stil, aber keiner von beiden kann wirklich als richtig bezeichnet werden. Das Einschließen eines Headers für jede Klasse führt mit größerer Wahrscheinlichkeit zu lästigen auftragsbezogenen Kompilierungsfehlern. Diese Kompilierungsfehler spiegeln jedoch auch Probleme wider, die durch sorgfältiges Codieren behoben werden könnten (obwohl es sich nicht lohnt, sie zu beheben).
Und ja, Sie werden diese auftragsbezogenen Probleme haben, sobald Sie anfangen, an
friend
Land zu kommen.Sie können sich das Problem in zwei Fällen vorstellen.
Fall 1: Sie haben eine kleine Anzahl von Klassen, die miteinander interagieren, beispielsweise weniger als ein Dutzend. Sie fügen diese Header regelmäßig hinzu, entfernen sie und ändern sie auf andere Weise, so dass sich dies auf ihre gegenseitigen Abhängigkeiten auswirkt. Dies ist der Fall, den Ihr Codebeispiel vorschlägt.
Die Kopfzeilen sind so klein, dass es nicht schwierig ist, auftretende Probleme zu lösen. Alle schwierigen Probleme werden behoben, indem ein oder zwei Header neu geschrieben werden. Wenn Sie sich Gedanken über Ihre Header-Strategie machen, lösen Sie Probleme, die es nicht gibt.
Fall 2: Sie haben Dutzende von Klassen. Einige der Klassen stellen das Rückgrat Ihres Programms dar, und das Umschreiben ihrer Header würde Sie dazu zwingen, einen großen Teil Ihrer Codebasis neu zu schreiben / neu zu kompilieren. Andere Klassen verwenden dieses Rückgrat, um Dinge zu erreichen. Dies ist ein typisches Geschäftsumfeld. Überschriften sind über Verzeichnisse verteilt und Sie können sich nicht realistisch an die Namen von allem erinnern.
Lösung: An diesem Punkt müssen Sie sich Ihre Klassen in logischen Gruppen vorstellen und diese Gruppen in Überschriften zusammenfassen, die Sie davon abhalten, immer wieder neu zu beginnen
#include
. Dies vereinfacht nicht nur das Leben, sondern ist auch ein notwendiger Schritt, um vorkompilierte Header zu nutzen .Sie beenden den
#include
Unterricht, den Sie nicht brauchen, aber wen interessiert das ?In diesem Fall würde Ihr Code so aussehen ...
quelle