52 Stimmen

Ihre bevorzugte C/C++-Header-Politik für große Projekte?

Haben Sie bei der Arbeit an einem großen C/C++-Projekt bestimmte Regeln für die #include in Quell- oder Header-Dateien?

Wir können uns zum Beispiel vorstellen, eine dieser beiden übertriebenen Regeln zu befolgen:

  1. #include sind untersagt in .h Dateien; es obliegt jedem .c Datei, um alle benötigten Header einzuschließen
  2. Jede .h Datei sollte alle ihre Abhängigkeiten enthalten, d.h. sie sollte allein ohne Fehler kompiliert werden können.

Ich nehme an, dass es bei jedem Projekt einen Kompromiss gibt, aber was ist Ihrer? Haben Sie genauere Regeln? Oder einen Link, der für eine der Lösungen spricht?

42voto

Mecki Punkte 113876

Wenn Sie H-Dateien ausschließlich in C-Dateien einbinden, kann das Einbinden einer H-Datei in eine C-Datei dazu führen, dass die Kompilierung fehlschlägt. Es könnte scheitern, weil Sie vielleicht 20 andere H-Dateien im Voraus einbinden müssen, und noch schlimmer, Sie müssen sie in der richtigen Reihenfolge einbinden. Bei sehr vielen H-Dateien wird dieses System auf lange Sicht zu einem administrativen Alptraum. Sie wollten nur eine H-Datei einbinden und haben am Ende zwei Stunden damit verbracht, herauszufinden, welche anderen H-Dateien in welcher Reihenfolge Sie ebenfalls einbinden müssen.

Wenn eine H-Datei nur dann erfolgreich in eine C-Datei eingebunden werden kann, wenn zuvor eine andere H-Datei eingebunden wurde, dann sollte die erste H-Datei die zweite einbinden und so weiter. Auf diese Weise können Sie einfach jede H-Datei in jede beliebige C-Datei einbinden, ohne befürchten zu müssen, dass die Kompilierung dadurch unterbrochen wird. Auf diese Weise geben Sie nur Ihre direkten Abhängigkeiten an, aber wenn diese Abhängigkeiten selbst auch Abhängigkeiten haben, liegt es an ihnen, diese anzugeben.

Andererseits sollten Sie keine H-Dateien in H-Dateien einbinden, wenn dies nicht notwendig ist. hashtable.h sollte nur andere Header-Dateien enthalten, die für die Verwendung Ihrer Hashtable-Implementierung erforderlich sind. Wenn die Implementierung selbst hashing.h und fügen sie dann in hashtable.c , nicht in hashtable.h da nur die Implementierung sie benötigt, nicht der Code, der nur die endgültige Hashtabelle verwenden möchte.

17voto

PierreBdR Punkte 40155

Ich halte beide vorgeschlagenen Regeln für schlecht. Ich für meinen Teil wende sie immer an:

Fügen Sie nur die Header-Dateien ein, die zum Kompilieren einer Datei erforderlich sind, und verwenden Sie nur das, was in diesem Header definiert ist. Dies bedeutet:

  1. Alle Objekte, die nur als Verweis oder Zeiger vorliegen, sollten vorwärts deklariert werden
  2. Alle Kopfzeilen, die Funktionen oder Objekte definieren, die in der Kopfzeile selbst verwendet werden, sind einzubeziehen.

13voto

paercebal Punkte 78198

Ich würde die Regel 2 anwenden:

Alle Header sollten autark sein, sei es durch:

  • keine anderweitig definierten Elemente verwenden
  • Vorwärtsdeklaration von anderswo definierten Symbolen
  • einschließlich der Kopfzeilen, die die Symbole definieren, die nicht weiterdeklariert werden können.

Wenn Sie also eine leere C/C++-Quelldatei haben, sollte das Einfügen eines Headers korrekt kompiliert werden.

Fügen Sie dann in die C/C++-Quelldatei nur das Notwendigste ein: Wenn HeaderA ein in HeaderB definiertes Symbol weiterdeklariert, und Sie dieses Symbol verwenden, müssen Sie beide einschließen... Die gute Nachricht ist, dass Sie, wenn Sie das vorwärts deklarierte Symbol nicht verwenden, nur HeaderA einbinden können und HeaderB nicht einbinden müssen.

Beachten Sie, dass das Spielen mit Templates diese Überprüfung "leerer Quelltext einschließlich Ihrer Header sollte kompilieren" etwas komplizierter (und amüsanter) macht...)

8voto

Konrad Rudolph Punkte 503837

Die erste Regel schlägt fehl, sobald es zirkuläre Abhängigkeiten gibt. Sie kann also nicht strikt angewendet werden.

(Das lässt sich zwar bewerkstelligen, verlagert aber eine ganze Menge Arbeit vom Programmierer auf den Nutzer dieser Bibliotheken, was offensichtlich falsch ist).

Ich bin für Regel 2 (obwohl es vielleicht gut wäre, "Vorwärtserklärungs-Kopfzeilen" anstelle der echten Kopfzeilen aufzunehmen, wie in <iosfwd> weil dies die Kompilierzeit verkürzt). Generell halte ich es für eine Art Selbstdokumentation, wenn eine Header-Datei "deklariert", welche Abhängigkeiten sie hat - und wie könnte man das besser tun, als die benötigten Dateien einzubinden?

EDIT:

In den Kommentaren wurde mir entgegengehalten, dass zirkuläre Abhängigkeiten zwischen Kopfzeilen ein Zeichen für schlechtes Design sind und vermieden werden sollten.

Das ist nicht richtig. In der Tat, zirkuläre Abhängigkeiten zwischen den Klassen können unvermeidlich sein und sind keineswegs ein Zeichen für schlechtes Design. Beispiele gibt es viele, ich möchte nur das Beobachter-Muster erwähnen, das einen kreisförmigen Bezug zwischen dem Beobachter und dem Subjekt aufweist.

Um die Zirkularität zwischen Klassen aufzulösen, müssen Sie eine Vorwärtsdeklaration verwenden, da die Reihenfolge der Deklaration in C++ wichtig ist. Nun ist es durchaus akzeptabel, diese Vorwärtsdeklaration zirkulär zu behandeln, um die Anzahl der Gesamtdateien zu reduzieren und den Code zu zentralisieren. Zugegeben, der folgende Fall passt nicht zu diesem Szenario, da es nur eine einzige Vorwärtsdeklaration gibt. Ich habe jedoch an einer Bibliothek gearbeitet, in der dies viel häufiger der Fall war.

// observer.hpp

class Observer; // Forward declaration.

#ifndef MYLIB_OBSERVER_HPP
#define MYLIB_OBSERVER_HPP

#include "subject.hpp"

struct Observer {
    virtual ~Observer() = 0;
    virtual void Update(Subject* subject) = 0;
};

#endif

// subject.hpp
#include <list>

struct Subject; // Forward declaration.

#ifndef MYLIB_SUBJECT_HPP
#define MYLIB_SUBJECT_HPP

#include "observer.hpp"

struct Subject {
    virtual ~Subject() = 0;
    void Attach(Observer* observer);
    void Detach(Observer* observer);
    void Notify();

private:
    std::list<Observer*> m_Observers;
};

#endif

5voto

Ferruccio Punkte 96076
  1. Halten Sie immer eine Art von Kopfschutz bereit.
  2. Verschmutzen Sie nicht den globalen Namensraum des Benutzers, indem Sie irgendwelche using namespace Anweisungen in einer Kopfzeile.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X