it-swarm.com.de

C ++ - Code in Header-Dateien

Mein persönlicher Stil mit C++ besteht darin, Klassendeklarationen immer in eine Include-Datei und Definitionen in eine CPP-Datei einzufügen, ähnlich wie in Lokis Antwort auf C++ - Header-Dateien, Codetrennung = . Zugegeben, ein Teil des Grundes, warum ich diesen Stil mag, hat wahrscheinlich mit all den Jahren zu tun, die ich damit verbracht habe, Modula-2 und Ada zu codieren, die beide ein ähnliches Schema mit Spezifikationsdateien und Body-Dateien haben.

Ich habe einen Kollegen, der viel besser mit C++ vertraut ist als ich, der darauf besteht, dass alle C++ - Deklarationen, soweit möglich, die Definitionen genau dort in die Header-Datei aufnehmen. Er sagt nicht, dass dies ein gültiger Alternativstil oder sogar ein etwas besserer Stil ist, sondern vielmehr, dass dies der neue allgemein akzeptierte Stil ist, den jetzt jeder für C++ verwendet.

Ich bin nicht mehr so ​​geschmeidig wie früher, also bin ich nicht wirklich darauf bedacht, auf seinen Zug aufzusteigen, bis ich dort oben noch ein paar Leute mit ihm sehe. Wie verbreitet ist diese Redewendung also wirklich?

Um den Antworten eine gewisse Struktur zu geben: Ist es jetzt der Weg , sehr verbreitet, etwas verbreitet, ungewöhnlich oder nervenaufreibend verrückt?

180
T.E.D.

Ihr Kollege hat Unrecht, und es war und ist üblich, Code in CPP-Dateien (oder in beliebige Erweiterungen) und Deklarationen in Kopfzeilen einzufügen.

Es ist gelegentlich sinnvoll, Code in den Header einzufügen. Dadurch kann der Compiler geschickter Inlinen. Gleichzeitig kann dies Ihre Kompilierungszeiten zerstören, da der gesamte Code jedes Mal verarbeitet werden muss, wenn er vom Compiler aufgenommen wird.

Schließlich ist es oft ärgerlich, kreisförmige Objektbeziehungen zu haben (manchmal erwünscht), wenn der gesamte Code die Überschriften sind.

Unterm Strich hattest du recht, er liegt falsch.

EDIT: Ich habe über Ihre Frage nachgedacht. Es gibt einen Fall, in dem das, was er sagt, wahr ist. Vorlagen. Viele neuere "moderne" Bibliotheken wie "Boost" verwenden Vorlagen häufig nur als "Header". Dies sollte jedoch nur beim Umgang mit Vorlagen erfolgen, da dies der einzige Weg ist, um mit Vorlagen umzugehen.

EDIT: Einige Leute möchten etwas mehr Klarheit, hier ein paar Gedanken zu den Nachteilen des Schreibens von "nur Header" -Code:

Wenn Sie sich umschauen, werden Sie eine ganze Menge Leute sehen, die versuchen, die Kompilierzeiten beim Umgang mit Boost zu verkürzen. Zum Beispiel: So verkürzen Sie die Kompilierungszeiten mit Boost Asio , wobei eine 14-Sekunden-Kompilierung einer einzelnen 1-KB-Datei mit Boost angezeigt wird. 14s scheinen nicht "explodiert" zu sein, aber es ist sicherlich viel länger als typisch und kann sich recht schnell summieren. Bei einem großen Projekt. Bibliotheken, die nur Header enthalten, beeinflussen die Kompilierungszeiten auf sehr messbare Weise. Wir tolerieren es nur, weil Boost so nützlich ist.

Darüber hinaus gibt es viele Dinge, die nicht nur in Kopfzeilen ausgeführt werden können (selbst boost verfügt über Bibliotheken, auf die Sie für bestimmte Teile wie Threads, Dateisysteme usw. verweisen müssen). Ein primäres Beispiel ist, dass Sie keine einfachen globalen Objekte nur in Header-Bibliotheken haben können (es sei denn, Sie greifen auf die Abscheulichkeit zurück, die ein Singleton ist), da Sie auf mehrere Definitionsfehler stoßen werden. HINWEIS: Mit den Inline-Variablen von C++ 17 kann dieses Beispiel in Zukunft ausgeführt werden.

Abschließend wird bei der Verwendung von boost als Beispiel für reinen Header-Code häufig ein großes Detail übersehen.

Boost ist eine Bibliothek, kein Code auf Benutzerebene. es ändert sich also nicht so oft. Wenn Sie im Benutzercode alles in Überschriften einfügen, müssen Sie bei jeder kleinen Änderung das gesamte Projekt neu kompilieren. Dies ist eine enorme Zeitverschwendung (und gilt nicht für Bibliotheken, die sich nicht von Kompilierung zu Kompilierung ändern). Wenn Sie Dinge zwischen Header/Quelle und besser aufteilen, verwenden Sie Forward-Deklarationen, um Includes zu reduzieren, und sparen Sie Stunden beim Neukompilieren, wenn Sie diese über einen Tag aufsummieren.

196
Evan Teran

An dem Tag, an dem sich die C++ - Programmierer auf The Way einigen, werden sich Lämmer mit Löwen anlegen, Palästinenser Israelis umarmen und Katzen und Hunde heiraten dürfen.

Die Trennung zwischen .h- und .cpp-Dateien ist zu diesem Zeitpunkt größtenteils willkürlich, ein Überbleibsel von Compiler-Optimierungen, die lange zurückliegen. Für mich gehören Deklarationen in den Header und Definitionen in die Implementierungsdatei. Aber das ist nur eine Gewohnheit, keine Religion.

147

Code in Headern ist im Allgemeinen eine schlechte Idee, da hierdurch eine Neukompilierung aller Dateien erzwungen wird, die den Header enthalten, wenn Sie den tatsächlichen Code und nicht die Deklarationen ändern. Dies verlangsamt auch die Kompilierung, da Sie den Code in jeder Datei analysieren müssen, die den Header enthält.

Ein Grund für die Verwendung von Code in Header-Dateien ist, dass das Schlüsselwort inline im Allgemeinen ordnungsgemäß funktioniert und Vorlagen verwendet werden, die in anderen cpp-Dateien instanziiert werden.

25
Laserallan

Was Sie vielleicht darüber informieren könnte, ist die Vorstellung, dass der meiste C++ - Code als Vorlage für eine maximale Benutzerfreundlichkeit dienen sollte. Und wenn es als Vorlage dient, muss sich alles in einer Header-Datei befinden, damit der Client-Code es sehen und instanziieren kann. Wenn es für Boost und die STL gut genug ist, ist es gut genug für uns.

Ich bin mit dieser Sichtweise nicht einverstanden, aber es kann sein, dass es dort herkommt.

19
JohnMcG

Ich denke, Ihr Kollege ist schlau und Sie haben auch Recht.

Die nützlichen Dinge, die ich gefunden habe, die alles in die Überschriften setzen, sind:

  1. Keine Notwendigkeit zum Schreiben und Synchronisieren von Headern und Quellen.

  2. Die Struktur ist einfach und keine kreisförmigen Abhängigkeiten zwingen den Codierer, eine "bessere" Struktur zu erstellen.

  3. Portabel, einfach in ein neues Projekt einzubetten.

Ich bin mit dem Kompilierungsproblem einverstanden, aber ich denke, wir sollten Folgendes beachten:

  1. Durch die Änderung der Quelldatei werden sehr wahrscheinlich die Header-Dateien geändert, was dazu führt, dass das gesamte Projekt erneut kompiliert wird.

  2. Die Kompilierungsgeschwindigkeit ist viel höher als zuvor. Und wenn Sie ein Projekt haben, das mit langer Zeit und hoher Frequenz erstellt werden soll, kann dies darauf hinweisen, dass Ihr Projektdesign Fehler aufweist. Durch die Aufteilung der Aufgaben in verschiedene Projekte und Module kann dieses Problem vermieden werden.

Zuletzt möchte ich Ihren Kollegen nur aus meiner persönlichen Sicht unterstützen.

13
XU Bin

Oft füge ich triviale Member-Funktionen in die Header-Datei ein, damit sie eingebunden werden können. Aber um den gesamten Code dort abzulegen, nur um mit den Vorlagen übereinzustimmen? Das ist schlicht verrückt.

Denken Sie daran: Eine dumme Konsequenz ist der Hobgoblin der kleinen Geister .

12
Mark Ransom

Wie Tuomas sagte, sollte Ihr Header minimal sein. Zum Abschluss werde ich noch etwas erweitern.

Ich persönlich verwende 4 Arten von Dateien in meinem C++ Projekte:

  • Öffentlich:
  • Weiterleitungs-Header: Bei Vorlagen usw. erhält diese Datei die Weiterleitungsdeklarationen, die im Header erscheinen.
  • Header: Diese Datei enthält den Forwarding-Header (falls vorhanden) und deklariert alles, was ich öffentlich machen möchte (und definiert die Klassen ...).
  • Privat:
  • Privater Header: Diese Datei ist ein Header, der für die Implementierung reserviert ist. Sie enthält den Header und deklariert die Hilfsfunktionen/-strukturen (zum Beispiel für Pimpl oder Prädikate). Überspringen, falls nicht erforderlich.
  • Quelldatei: Enthält den privaten Header (oder den Header, wenn kein privater Header vorhanden ist) und definiert alles (Nicht-Template ...)

Außerdem verbinde ich dies mit einer anderen Regel: Definiere nicht, was du weiterleiten kannst. Natürlich bin ich dort vernünftig (die Verwendung von Pimpl ist überall ein ziemlicher Aufwand).

Das heißt, ich bevorzuge eine Forward-Deklaration gegenüber einer #include Anweisung in meinen Kopfzeilen, wann immer ich mit ihnen durchkommen kann.

Schließlich verwende ich auch eine Sichtbarkeitsregel: Ich beschränke den Geltungsbereich meiner Symbole so weit wie möglich, damit sie die äußeren Geltungsbereiche nicht verschmutzen.

Alles in allem:

// example_fwd.hpp
// Here necessary to forward declare the template class,
// you don't want people to declare them in case you wish to add
// another template symbol (with a default) later on
class MyClass;
template <class T> class MyClassT;

// example.hpp
#include "project/example_fwd.hpp"

// Those can't really be skipped
#include <string>
#include <vector>

#include "project/pimpl.hpp"

// Those can be forward declared easily
#include "project/foo_fwd.hpp"

namespace project { class Bar; }

namespace project
{
  class MyClass
  {
  public:
    struct Color // Limiting scope of enum
    {
      enum type { Red, Orange, Green };
    };
    typedef Color::type Color_t;

  public:
    MyClass(); // because of pimpl, I need to define the constructor

  private:
    struct Impl;
    pimpl<Impl> mImpl; // I won't describe pimpl here :p
  };

  template <class T> class MyClassT: public MyClass {};
} // namespace project

// example_impl.hpp (not visible to clients)
#include "project/example.hpp"
#include "project/bar.hpp"

template <class T> void check(MyClass<T> const& c) { }

// example.cpp
#include "example_impl.hpp"

// MyClass definition

Der Lebensretter dabei ist, dass der Forward-Header meistens unbrauchbar ist: Nur bei typedef oder template erforderlich, ebenso wie der Implementierungs-Header;)

6
Matthieu M.

Wenn ich eine neue Klasse schreibe, werde ich im Allgemeinen den gesamten Code in die Klasse einfügen, damit ich nicht in einer anderen Datei danach suchen muss. Nachdem alles funktioniert, teile ich den Inhalt der Methoden in die cpp-Datei auf Belassen Sie die Prototypen in der hpp-Datei.

5
EvilTeach

Um mehr Spaß hinzuzufügen, können Sie .ipp - Dateien hinzufügen, die die Vorlagenimplementierung enthalten (die in .hpp Enthalten ist), während .hpp Die Schnittstelle enthält.

Abgesehen von templatisiertem Code (je nach Projekt können dies die Mehrheit oder die Minderheit der Dateien sein) gibt es normalen Code und hier ist es besser, die Deklarationen und Definitionen zu trennen. Stellen Sie bei Bedarf auch Voraberklärungen zur Verfügung - dies kann sich auf die Kompilierungszeit auswirken.

5
Anonymous

Ich persönlich mache das in meinen Header-Dateien:

// class-declaration

// inline-method-declarations

Ich mag es nicht, den Code für die Methoden in die Klasse zu mischen, da ich es als mühsam empfinde, schnell nachzuschlagen.

Ich würde nicht alle Methoden in die Header-Datei setzen. Der Compiler kann (normalerweise) keine virtuellen Methoden einbinden und (wahrscheinlich) nur kleine Methoden ohne Schleifen einbinden (hängt völlig vom Compiler ab).

Die Methoden in der Klasse anzuwenden ist gültig ... aber aus Sicht der Lesbarkeit mag ich es nicht. Das Einfügen der Methoden in den Header bedeutet, dass sie, wenn möglich, eingebunden werden.

4
TofuBeer

Wenn dieser neue Weg wirklich der Weg ist, sind wir in unseren Projekten möglicherweise in eine andere Richtung gelaufen.

Weil wir versuchen, alle unnötigen Dinge in Überschriften zu vermeiden. Dies schließt die Vermeidung von Header-Kaskaden ein. Code in Headern benötigt wahrscheinlich einen anderen Header, für den ein anderer Header usw. erforderlich ist. Wenn wir gezwungen sind, Vorlagen zu verwenden, vermeiden wir es, Header mit Vorlagenmaterial zu sehr zu verunreinigen.

Wir benutzen auch "opaque pointer" -Pattern falls zutreffend.

Mit diesen Methoden können wir schneller bauen als die meisten unserer Kollegen. Und ja ... das Ändern von Code oder Klassenmitgliedern wird keine großen Neuerstellungen verursachen.

4
Virne

Ich halte es für absolut absurd, ALLE Funktionsdefinitionen in die Header-Datei aufzunehmen. Warum? Weil die Header-Datei als PUBLIC-Schnittstelle für Ihre Klasse verwendet wird. Es ist die Außenseite der "Black Box".

Wenn Sie sich eine Klasse ansehen müssen, um zu verweisen, wie sie verwendet wird, sollten Sie sich die Header-Datei ansehen. Die Header-Datei sollte eine Liste der möglichen Aktionen enthalten (kommentiert, um die Einzelheiten der Verwendung der einzelnen Funktionen zu beschreiben) und eine Liste der Member-Variablen enthalten. Es SOLLTE NICHT beinhalten, WIE jede einzelne Funktion implementiert wird, da dies eine Schiffsladung unnötiger Informationen ist und nur die Header-Datei überfüllt.

2
SeanRamey

Ich habe die gesamte Implementierung aus der Klassendefinition herausgenommen. Ich möchte die Doxy-Kommentare aus der Klassendefinition entfernen.

2
Jesus Fernandez

IMHO, Er hat NUR das Verdienst, wenn er Vorlagen und/oder Metaprogramme erstellt. Es gibt viele Gründe, die bereits erwähnt wurden, dass Sie Header-Dateien auf Deklarationen beschränken. Das sind nur ... Überschriften. Wenn Sie Code einbinden möchten, kompilieren Sie ihn als Bibliothek und verknüpfen ihn.

2
spoulson

Hängt das nicht wirklich von der Komplexität des Systems und den internen Konventionen ab?

Im Moment arbeite ich an einem neuronalen Netzwerksimulator, der unglaublich komplex ist, und der akzeptierte Stil, den ich verwenden soll, ist:

Klassendefinitionen in classname.h
Klassencode in classnameCode.h
ausführbarer Code in classname.cpp

Dadurch werden die vom Benutzer erstellten Simulationen von den vom Entwickler erstellten Basisklassen getrennt und funktionieren in der jeweiligen Situation am besten.

Es würde mich jedoch überraschen, wenn Leute dies beispielsweise in einer Grafikanwendung oder einer anderen Anwendung tun, die nicht dazu dient, Benutzern eine Codebasis zur Verfügung zu stellen.

1
Ed James

Ich denke, Ihr Mitarbeiter hat Recht, solange er nicht in den Prozess eingibt, um ausführbaren Code in den Header zu schreiben. Ich denke, das richtige Gleichgewicht besteht darin, dem von GNAT Ada angegebenen Pfad zu folgen, in dem die .ads-Datei eine vollkommen adäquate Schnittstellendefinition des Pakets für seine Benutzer und für seine Kinder enthält.

Übrigens, Ted, haben Sie sich in diesem Forum die letzte Frage über die Ada-Bindung an die CLIPS-Bibliothek angesehen, die Sie vor einigen Jahren geschrieben haben und die nicht mehr verfügbar ist (relevante Webseiten sind jetzt geschlossen). Auch wenn diese Bindung zu einer alten Clips-Version gemacht wurde, könnte sie ein gutes Startbeispiel für jemanden sein, der die CLIPS-Inferenz-Engine in einem Ada 2012-Programm verwenden möchte.

0
Emile

Vorlagencode sollte nur in Kopfzeilen sein. Abgesehen davon sollten alle Definitionen außer Inlines in .cpp sein. Das beste Argument dafür wären die std-Bibliotheksimplementierungen, die der gleichen Regel folgen. Sie würden nicht anderer Meinung sein, wenn die Entwickler von std lib diesbezüglich Recht hätten.

0