it-swarm.com.de

Ist es empfehlenswert, sich darauf zu verlassen, dass Header transitiv eingefügt werden?

Ich bereinige die Includes in einem C++ - Projekt, an dem ich arbeite, und frage mich immer wieder, ob ich alle direkt in einer bestimmten Datei verwendeten Header 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 Vorwärtsdeklaration für RenderObject keine Option ist.)

Jetzt weiß ich, dass RenderObject.hpp enthält Texture.hpp - Ich weiß das, weil jedes RenderObject ein Texture Mitglied hat. Trotzdem füge ich ausdrücklich Texture.hpp im Entity.hpp, weil ich nicht sicher bin, ob es eine gute Idee ist, sich darauf zu verlassen, dass es in RenderObject.hpp.

Also: Ist es eine gute Praxis oder nicht?

39
futlib

Sie sollten immer alle Header in diese Datei aufnehmen, die alle in einer CPP-Datei verwendeten Objekte definieren, unabhängig davon, was Sie über die Inhalte dieser Dateien wissen. Sie sollten Schutzvorrichtungen in alle Header-Dateien aufnehmen, um sicherzustellen, dass das mehrfache Einschließen von Headern keine Rolle spielt.

Die Gründe:

  • Dies macht Entwicklern, die die Quelle genau lesen, klar, was die betreffende Quelldatei erfordert. Hier kann jemand, der sich die ersten Zeilen in der Datei ansieht, sehen, dass es sich um Texture Objekte in dieser Datei handelt.
  • Dies vermeidet Probleme, bei denen überarbeitete Header Kompilierungsprobleme verursachen, wenn sie selbst keine bestimmten Header mehr benötigen. Angenommen, Sie erkennen, dass RenderObject.hpp braucht eigentlich nicht Texture.hpp selbst.

Eine Konsequenz ist, dass Sie niemals einen Header in einen anderen Header aufnehmen sollten, es sei denn, er wird in dieser Datei ausdrücklich benötigt.

66
Gort the Robot

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, B jedoch nicht selbst, schließen Sie nur A.h.

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 benötigen, fügen Sie es nur dort ein: Dies ist der Unterschied zwischen einer öffentlichen und einer privaten Abhängigkeit und verhindert, dass Benutzer Ihrer Klasse Header ziehen, die sie nicht wirklich benötigen.

23
Nir Friedman

Ich frage mich immer wieder, ob ich explizit alle Header einschließen soll, die direkt in einer bestimmten Datei verwendet werden

Ja.

Sie wissen nie, wann sich diese anderen Header ändern könnten. Es ist weltweit sinnvoll, in jede Übersetzungseinheit die Überschriften aufzunehmen, die die Übersetzungseinheit benötigt.

Wir haben Kopfschutz, um sicherzustellen, dass die doppelte Einbeziehung nicht schädlich ist.

In dieser Hinsicht gehen die Meinungen auseinander, aber ich bin der Ansicht, dass jede Datei (ob c/cpp-Quelldatei oder h/hpp-Header-Datei) für sich kompiliert oder analysiert werden kann.

Daher sollten alle Dateien # alle benötigten Header-Dateien enthalten - Sie sollten nicht davon ausgehen, dass bereits eine Header-Datei zuvor 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 anderer Stelle definiert ist, ohne es direkt einzuschließen. Sie müssen also suchen (und möglicherweise die falsche finden!).

Auf der anderen Seite spielt es keine Rolle (in der Regel), ob Sie eine Datei einschließen, die Sie nicht benötigen ...


Aus persönlichen Gründen ordne ich # include-Dateien in alphabetischer Reihenfolge an, aufgeteilt in System und Anwendung - dies trägt dazu bei, die "in sich geschlossene und vollständig kohärente" Nachricht zu verstärken.

3
Andrew

Es kann einen anderen Fall geben: Sie haben A.h, B.h und Ihr C.cpp, B.h enthält A.h.

in C.cpp können Sie also schreiben

#include "B.h"
#include "A.h" // < this can be optional as B.h already has all the stuff in A.h

Was kann also passieren, wenn Sie hier nicht #include "A.h" schreiben? 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, aber B.h dort gelassen.

Wenn Sie sowohl A.h als auch B.h einschließen und jetzt an diesem Punkt, können Tools, die unnötige Includes erkennen, Ihnen helfen, darauf hinzuweisen, dass B.h Include nicht mehr benötigt wird. Wenn Sie nur B.h wie oben einschließen, ist es für Tools/Human schwierig, das unnötige Include nach Ihrer Codeänderung zu erkennen.

2
bugs king

Es hängt davon ab, ob diese transitive Einbeziehung notwendigerweise (z. B. Basisklasse) oder aufgrund eines Implementierungsdetails (privates Mitglied) erfolgt.

Zur Verdeutlichung ist die transitive Einbeziehung erforderlich, wenn das Entfernen erst nach dem ersten Ändern der im Zwischenheader deklarierten Schnittstellen erfolgen kann. Da dies bereits eine wichtige Änderung ist, muss jede CPP-Datei, die sie verwendet, trotzdem überprüft werden.

Beispiel: A.h ist in B.h enthalten, das von C.cpp verwendet wird. Wenn B.h A.h für einige Implementierungsdetails verwendet hat, sollte C.cpp nicht davon ausgehen, dass B.h dies weiterhin tut. Wenn B.h A.h für eine Basisklasse verwendet, kann C.cpp davon ausgehen, dass B.h weiterhin die relevanten Header für seine Basisklassen enthält.

Sie sehen hier den tatsächlichen Vorteil, dass Header-Einschlüsse NICHT dupliziert werden. Angenommen, die von B.h verwendete Basisklasse gehörte wirklich nicht zu A.h und wurde in B.h selbst umgestaltet. B.h ist jetzt ein eigenständiger Header. Wenn C.cpp A.h redundant enthält, enthält es jetzt einen unnötigen Header.

2
MSalters

Ich verfolge einen ähnlichen, etwas anderen Ansatz als die vorgeschlagenen Antworten.

Geben Sie in Headern immer nur ein Minimum an, genau das, was für den Kompilierungsdurchlauf erforderlich ist. Verwenden Sie nach Möglichkeit eine Vorwärtserklärung.

In den Quelldateien ist es nicht so wichtig, wie viel Sie einschließen. Meine Präferenzen sind immer noch ein Minimum, um es zu bestehen.

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 zum Kompilieren die neueste Hardware verwendet wird, kann der Unterschied spürbar sein. Der Grund ist, dass der Compiler den enthaltenen Header noch öffnen und analysieren muss. Um den Build zu optimieren, wenden Sie die oben beschriebene Technik an (schließen Sie das Minimum ein und verwenden Sie die Vorwärtsdeklaration).

Obwohl etwas veraltet, erklärt C++ - Software-Design im großen Maßstab (von John Lakos) dies alles im Detail.

1
BЈовић