Sollte ich viele Funktionen in eine Datei schreiben? Oder mehr oder weniger eine Funktion pro Datei?

8

Ich liebe es, meinen Code zu organisieren, also idealerweise möchte ich eine Klasse pro Datei oder, wenn ich nicht-Mitglied Funktionen habe, eine Funktion pro Datei.

Die Gründe sind:

  1. Wenn ich den Code lese, werde ich immer weiß in welcher Datei ich eine finden sollte bestimmte Funktion oder Klasse.

  2. Wenn es eine Klasse oder ein Nichtmitglied ist Funktion pro Header-Datei, dann werde ich nicht schließe eine ganze Sauerei mit ein, wenn ich include eine Header-Datei.

  3. Wenn ich eine kleine Änderung in einer Funktion mache, muss nur diese Funktion neu kompiliert werden.

Die Aufteilung in viele Header und viele Implementierungsdateien kann jedoch die Kompilierung erheblich verzögern. In meinem Projekt greifen die meisten Funktionen auf eine bestimmte Anzahl von anderen Bibliotheksfunktionen zu. Dieser Code wird also für jede Implementierungsdatei einmal und immer wieder kompiliert. Das Kompilieren meines gesamten Projekts dauert derzeit etwa 45 Minuten auf einer Maschine. Es gibt ungefähr 50 Objektdateien, und jede verwendet dieselben teuren zu kompilierenden Header.

Vielleicht ist es akzeptabel, pro Header -Datei eine Klasse (oder Nicht-Member-Funktion) zu haben, aber die Implementierungen vieler oder aller dieser Funktionen in eins zu setzen Implementierungsdatei, wie im folgenden Beispiel?

%Vor%

Auch hier wäre der Vorteil, dass ich nur die foo-Funktion oder nur die bar-Funktion einschließen kann, und die Kompilierung des gesamten Projekts wird schneller sein, weil foobar.cpp eine Datei ist. co_de% (was hier nur ein Beispiel für eine andere kostspielig zu erstellende Vorlagenkonstruktion ist) muss nur einmal einkompiliert werden, im Gegensatz zu zweimal, wenn ich ein std::vector<int> und foo.cpp separat kompiliert habe. Natürlich ist mein Grund (3) für dieses Szenario nicht gültig: Nachdem ich nur foo () {...} geändert habe, muss ich die ganze, potentiell große, Datei bar.cpp neu kompilieren.

Ich bin gespannt, was Ihre Meinung ist!

    
Frank 10.02.2009, 05:20
quelle

9 Antworten

8

IMHO, sollten Sie Elemente in logische Gruppierungen kombinieren und erstellen Sie Ihre Dateien auf dieser Grundlage.

Wenn ich Funktionen schreibe, gibt es oft ein halbes Dutzend oder so, die eng miteinander verwandt sind. Ich neige dazu, sie in einer einzigen Header- und Implementierungsdatei zusammenzufassen.

Wenn ich Klassen schreibe, beschränke ich mich normalerweise auf eine Schwergewichtsklasse pro Header und Implementierungsdatei. Ich könnte einige Komfortfunktionen oder kleine Hilfsklassen hinzufügen.

Wenn ich finde, dass eine Implementierungsdatei Tausende von Zeilen lang ist, ist das normalerweise ein Zeichen dafür, dass da zu viel da ist und ich es aufteilen muss.

    
Mr Fooz 10.02.2009, 05:25
quelle
7

Eine Funktion pro Datei könnte meiner Meinung nach unordentlich werden. Stellen Sie sich vor, wenn POSIX- und ANSI C-Header auf die gleiche Weise erstellt wurden.

%Vor%

Eine Klasse pro Datei ist jedoch eine gute Idee.

    
dreamlax 10.02.2009 05:34
quelle
2

Wir verwenden das Prinzip einer externen Funktion pro Datei. Innerhalb dieser Datei können jedoch mehrere andere "Hilfsfunktionen" in unbenannten Namespaces vorhanden sein, die zum Implementieren dieser Funktion verwendet werden.

Nach unserer Erfahrung hatte dies im Gegensatz zu anderen Kommentaren zwei wesentliche Vorteile. Die ersten sind Build-Zeiten sind schneller als Module müssen nur neu erstellt werden, wenn ihre spezifischen APIs geändert werden. Der zweite Vorteil besteht darin, dass es bei Verwendung eines allgemeinen Benennungsschemas nie erforderlich ist, nach der Kopfzeile zu suchen, die die Funktion enthält, die Sie aufrufen möchten:

%Vor%

Ich stimme nicht zu, dass die Standardbibliothek ein gutes Beispiel dafür ist, keine (externe) Funktion pro Datei zu verwenden. Standardbibliotheken ändern sich nie und haben gut definierte Schnittstellen und daher trifft keiner der obigen Punkte auf sie zu.

Abgesehen davon, gibt es auch im Fall der Standardbibliothek einige potentielle Vorteile beim Aufteilen der einzelnen Funktionen. Der erste ist, dass Compiler eine hilfreiche Warnung generieren könnten, wenn unsichere Versionen von Funktionen verwendet werden, z. strcpy vs strncpy , ähnlich wie g ++ für die Aufnahme von vs.

warnte

Ein weiterer Vorteil ist, dass ich nicht länger durch die Aufnahme von Speicher erwischt würde, wenn ich memmove verwenden möchte!

    
Richard Corden 10.02.2009 10:50
quelle
1

Ich habe auch versucht, Dateien in einer Funktion pro Datei zu teilen, aber es hatte einige Nachteile. Manchmal neigen Funktionen dazu, größer zu werden als sie benötigen (Sie wollen nicht jedes Mal eine neue c-Datei hinzufügen), es sei denn, Sie sind fleißig beim Refactoring Ihres Codes (ich bin es nicht). Derzeit lege ich 1 bis 3 Funktionen in die c-Datei und gruppiere alle c-Dateien für eine Funktionalität in einem Verzeichnis. Für Header-Dateien habe ich Funcionality.h und Subfunctionality.h , so dass ich alle Funktionen auf einmal aufnehmen kann, wenn benötigt oder nur eine kleine Dienstprogrammfunktion, wenn das ganze Paket nicht benötigt wird.

    
Gerhard 10.02.2009 05:36
quelle
1

Sie können einige Ihrer Funktionen als statische Methoden einer oder mehrerer Klassen deklarieren: Dies gibt Ihnen die Möglichkeit (und eine gute Ausrede), mehrere davon in einer einzigen Quelldatei zu gruppieren.

Ein guter Grund, mehrere Funktionen in einer Quelldatei zu haben oder nicht, wenn diese Quellendatei eins zu eins mit Objektdateien ist und der Linker ganze Objektdateien verknüpft: wenn eine ausführbare Datei eine Funktion haben möchte, aber keine andere , dann legen Sie sie in separate Quelldateien (so dass der Linker eine ohne die andere verknüpfen kann).

    
ChrisW 10.02.2009 05:34
quelle
1

Ein alter Programmierprofessor von mir schlug vor, alle paar hundert Zeilen Code für die Wartbarkeit aufzubrechen. Ich entwickle nicht mehr in C ++, aber in C # beschränke ich mich auf eine Klasse pro Datei, und die Größe der Datei spielt keine Rolle, solange nichts mit meinem Objekt zu tun hat. Sie können #pragma-Regionen verwenden, um den Editor-Platz elegant zu reduzieren, nicht sicher, ob der C ++ - Compiler sie hat, aber wenn ja, dann verwenden Sie sie definitiv.

Wenn ich noch in C ++ programmieren würde, würde ich Funktionen gruppieren, indem ich mehrere Funktionen pro Datei benutze. Also habe ich vielleicht eine Datei namens 'Service.cpp' mit einigen Funktionen, die diesen "Dienst" definieren. Wenn Sie eine Funktion pro Datei haben, wird es bedauern, irgendwann irgendwie wieder in Ihr Projekt zurückzufinden.

Mehrere tausend Zeilen Code pro Datei sind jedoch manchmal nicht nötig. Funktionen selbst sollten nie mehr als ein paar hundert Zeilen Code sein. Denken Sie immer daran, dass eine Funktion nur eine Sache tun und minimal gehalten werden sollte. Wenn eine Funktion mehr als eine Sache macht, sollte sie in Hilfsmethoden umgewandelt werden.

Es tut auch nie weh, mehrere Quelldateien zu haben, die eine einzige Entität definieren. Dh: 'ServiceConnection.cpp' 'ServiceSettings.cpp', und so weiter.

Wenn ich ein einzelnes Objekt mache und andere Objekte besitzt, kombiniere ich mehrere Klassen in einer einzigen Datei. Zum Beispiel ein Button-Steuerelement, das 'ButtonLink'-Objekte enthält, könnte ich das in die Button-Klasse kombinieren. Manchmal tue ich das nicht, aber das ist eine "Präferenz des Augenblicks" Entscheidung.

Mach, was für dich am besten ist. Experimentieren Sie ein wenig mit verschiedenen Stilen in kleineren Projekten kann helfen. Hoffe, das hilft dir ein bisschen.

    
David Anderson - DCOM 10.02.2009 05:48
quelle
1

Für den HEADER-Teil sollten Sie Elemente zu logischen Gruppierungen kombinieren und darauf basierend Ihre HEADER-Dateien erstellen. Dies scheint und ist sehr logisch IMHO.

Für den SOURCE-Teil sollten Sie jede Funktionsimplementierung in eine separate SOURCE-Datei einfügen. (statische Funktionen sind in diesem Fall eine Ausnahme) Dies mag zunächst nicht logisch erscheinen, aber denken Sie daran, ein Compiler kennt die Funktionen, aber ein Linker kennt nur die o / obj-Dateien und ihre exportierten Symbole. Dies kann die Größe der Ausgabedatei erheblich verändern und dies ist ein sehr wichtiges Problem für eingebettete Systeme.

Checkout glibc oder Visual C ++ CRT-Quellbaum ...

    
Malkocoglu 10.02.2009 07:14
quelle
1

Ich kann einige Vorteile für Ihren Ansatz sehen, aber es gibt mehrere Nachteile.

1) Ein Paket ist ein Albtraum. Sie können mit 10-20 Includes enden, um die Funktionen zu erhalten, die Sie benötigen. Zum Beispiel Image, wenn STDIO oder StdLib auf diese Weise implementiert wurde.

2) Das Durchsuchen des Codes ist ein wenig mühsam, da es im Allgemeinen einfacher ist, durch eine Datei zu blättern als Dateien zu wechseln. Offensichtlich ist eine zu große Dateimenge schwierig, aber selbst bei modernen IDEs ist es ziemlich einfach, die Datei auf das zu reduzieren, was Sie brauchen, und viele von ihnen haben Abkürzungslisten.

3) machen die Pflege von Dateien ist ein Schmerz.

4) Ich bin ein großer Fan von kleinen Funktionen und Refactoring. Wenn Sie Overhead hinzufügen (eine neue Datei erstellen, zur Quellcodeverwaltung hinzufügen, ...), werden die Leute ermutigt, längere Funktionen zu schreiben. Statt 1 Funktion in 3 Teile zu zerlegen, erstellen Sie einfach 1 große.

    
hacken 10.02.2009 08:17
quelle
0

Eine Funktion pro Datei hat einen technischen Vorteil, wenn Sie eine statische Bibliothek erstellen (was vermutlich einer der Gründe ist, warum Projekte wie die Musl- libc Projekt folgen diesem Muster).

Statische Bibliotheken sind mit der Granularität von Objektdateien verknüpft, wenn Sie beispielsweise eine statische Bibliothek libfoobar.a aus *:

haben %Vor%

Wenn Sie die Bibliothek für die Funktion bar verknüpfen, wird das bar.o -Archivmitglied verknüpft, nicht jedoch das foo.o -Member. Wenn Sie für foo1 verknüpfen, wird das foo.o -Member verknüpft, wodurch die möglicherweise unnötige Funktion foo2 eingefügt wird.

Es gibt möglicherweise andere Möglichkeiten zu verhindern, dass nicht benötigte Funktionen in ( -ffunction-sections -fdata-sections und --gc-sections ) verknüpft werden, aber eine Funktion pro Datei ist wahrscheinlich am zuverlässigsten.

  • Ich ignoriere den C ++ - Namen, der hier aus Gründen der Einfachheit fehlt
PSkocik 22.10.2016 15:37
quelle