Forum: PC-Programmierung C++ Funktionen auslagern


von Janush B. (foxx)


Lesenswert?

Hallo,

ich möchte in C++ ein paar Funktionen die thematisch zusammengehören 
auslagern.
Dafür scheint das Konzept einer weiteren cpp und headerdatei Standard zu 
sein...? Dadurch muss man jede neue Funktion auch in der Headerdatei 
angeben und durch das getrennte kompilieren der main.cpp und 
funktionen.cpp funktionieren inline Funktionen vermutlich auch nicht 
mehr...?

Daher wäre es mir eigentlich lieber, dass der ausgelagerte Code so 
kompiliert wird, als wäre alles in der main Datei. Die Trennung soll 
also eigentlich nur mir zur Übersicht dienen. Das ganze Konstrukt mit 
header usw finde ich eher umständlich gibt es da nicht eine "bessere" 
Möglichkeit?

Viele Grüße

von Sheeva P. (sheevaplug)


Lesenswert?

Janush B. schrieb:
> ich möchte in C++ ein paar Funktionen die thematisch zusammengehören
> auslagern.
> Dafür scheint das Konzept einer weiteren cpp und headerdatei Standard zu
> sein...?

Ja, aber... (siehe unten).

> Dadurch muss man jede neue Funktion auch in der Headerdatei
> angeben und durch das getrennte kompilieren der main.cpp und
> funktionen.cpp funktionieren inline Funktionen vermutlich auch nicht
> mehr...?

Doch.

> Daher wäre es mir eigentlich lieber, dass der ausgelagerte Code so
> kompiliert wird, als wäre alles in der main Datei. Die Trennung soll
> also eigentlich nur mir zur Übersicht dienen. Das ganze Konstrukt mit
> header usw finde ich eher umständlich gibt es da nicht eine "bessere"
> Möglichkeit?

Die Auslagerung der Funktionssignaturen in Headerdateien ist vor allem 
dann notwendig, wenn eine Bibliothek im (kompilierten) Binärformat 
ausgeliefert werden soll. Damit anderer Code diese Bibliothek dann 
benutzen kann, muß er die Signaturen (Funktionsnamen, Parameter, 
Rückgabewerte) kennen, und die werden ihm dann durch die Headerdatei 
bekannt gemacht.

Ein weiterer Vorteil dieser Aufteilung zeigt sich bei großen Projekten 
mit vielen .c- oder .cpp-Dateien. Deren Objektdateien müssen dann 
nämlich nur einmal und ansonsten nur dann neu übersetzt werden, wenn sie 
auch geändert worden sind. Insofern spart diese Aufteilung in Source- 
und Headerdateien eine Menge unnötiger Compilierungen und somit Zeit.

Zuletzt dient die Aufteilung in Header- und Sourcedateien dazu, die 
Schnittstelle von der Implementierung zu trennen.

Für das, was Du vorhast, reicht es, die Funktionen in die Headerdatei zu 
schreiben -- also nicht nur die Funktionssignatur, sondern auch den Code 
der Funktion. Anders gesagt: Du kannst die Funktionen einfach so, wie 
sie aktuell in Deiner main.cpp stehen, 1:1 in eine Headerdatei kopieren 
(die notwendigen Includes nicht vergessen, wenn die Headerdatei auch 
noch in anderen Projekten genutzt werden solle) und die neue Headerdatei 
dann einfach mit "#include <headerdatei.hpp>" inkludieren. Die 
Präprozessor-Direktive "#include" macht nichts anderes, als den Code der 
inkludierten Datei dorthin zu kopieren, wo das "#include" steht.

Aber Achtung: das so in Deine main.cpp kopierte wird jedesmal 
mitübersetzt, wenn Du Deine main.cpp übersetzt. Das kostet natürlich 
jedesmal Zeit.

Um eventuelle Probleme durch ein mehrfaches Inkludieren einer 
Headerdatei zu vermeiden, haben sich sogenannte Safeguards etabliert:
1
#ifndef _HEADERDATEI_HPP
2
#define _HEADERDATEI_HPP
3
4
[...code here...]
5
6
#endif // _HEADERDATEI_HPP

Manche Compiler bieten dazu eine eigene Präprozessor-Direktive an, die 
aber nicht von allen Compilern unterstützt wird und daher nicht portabel 
ist:
1
#pragma once
2
3
[...code here...]

von Sven B. (scummos)


Lesenswert?

Sheeva P. schrieb:
> Du kannst die Funktionen einfach so, wie
> sie aktuell in Deiner main.cpp stehen, 1:1 in eine Headerdatei kopieren
> (die notwendigen Includes nicht vergessen, wenn die Headerdatei auch
> noch in anderen Projekten genutzt werden solle) und die neue Headerdatei
> dann einfach mit "#include <headerdatei.hpp>" inkludieren. Die
> Präprozessor-Direktive "#include" macht nichts anderes, als den Code der
> inkludierten Datei dorthin zu kopieren, wo das "#include" steht.

Kannst du machen, ist aber keine so tolle Idee; sobald der Header 
mehrfach benutzt wird, ist dann der Linker unglücklich. Die 
Include-Guards helfen dagegen auch nicht. Lieber den Code in die 
cpp-Datei schreiben und nur die Definition in den Header.

von Sheeva P. (sheevaplug)


Lesenswert?

Sven B. schrieb:
> Kannst du machen, ist aber keine so tolle Idee; sobald der Header
> mehrfach benutzt wird, ist dann der Linker unglücklich. Die
> Include-Guards helfen dagegen auch nicht.

Wie das? Bitte zeig' doch mal einen Anwendungsfall. Bis dahin bleibe ich 
bei meiner Überzeugung, daß alle standardkompatiblen Compiler, 
Präprozessor und Linker damit vollkommen glücklich sind.

> Lieber den Code in die cpp-Datei schreiben und nur die Definition
> in den Header.

Die Boost-Entwickler liefern die meisten ihrer Libs "header-only" aus 
[1]. Haben die keine Ahnung?

[1] 
http://www.boost.org/doc/libs/1_64_0/more/getting_started/windows.html#header-only-libraries

von Wilhelm M. (wimalopaan)


Lesenswert?

Hat man mehrere Übersetzungseinheiten, die jeweils identische 
Definitionen durchführen, verletzt man die one-definition-rule (ODR).

Deswegen:

- nur Deklarationen in Header
- Defininiton in cpp-Dateien

Templates selbst sind keine konkreter Code: sie werden erst später (in 
der cpp-Datei) instanziiert. Doppelte Instanziierungen in 
unterschiedlichen cpp-Dateien werden ggf. vom Linker wieder entfernt.

von Rolf M. (rmagnus)


Lesenswert?

Sheeva P. schrieb:
> Sven B. schrieb:
>> Kannst du machen, ist aber keine so tolle Idee; sobald der Header
>> mehrfach benutzt wird, ist dann der Linker unglücklich. Die
>> Include-Guards helfen dagegen auch nicht.
>
> Wie das? Bitte zeig' doch mal einen Anwendungsfall.

test.h:
1
#ifndef TEST_H                                                                                                                                                                                                                              
2
#define TEST_H                                                                                                                                                                                                                              
3
                                                                                                                                                                                                                                            
4
void funktion()                                                                                                                                                                                                                             
5
{                                                                                                                                                                                                                                           
6
}                                                                                                                                                                                                                                           
7
                                                                                                                                                                                                                                            
8
#endif // TEST_H

a.cpp:
1
#include "test.h"

main.cpp:
1
#include "test.h"
2
3
int main()
4
{
5
}

Ergebnis des Compilers:
1
$ g++ main.cpp a.cpp
2
/tmp/ccZBCtOk.o: In Funktion `funktion()':
3
a.cpp:(.text+0x0): Mehrfachdefinition von `funktion()'
4
/tmp/ccfUjpCV.o:main.cpp:(.text+0x0): first defined here
5
collect2: error: ld returned 1 exit status

> Bis dahin bleibe ich bei meiner Überzeugung, daß alle standardkompatiblen
> Compiler, Präprozessor und Linker damit vollkommen glücklich sind.

Es verletzt die "one definition rule", es sei denn, man markiert die 
Funktionen explizit als "inline" oder als "static".

> Die Boost-Entwickler liefern die meisten ihrer Libs "header-only" aus
> [1]. Haben die keine Ahnung?
>
> [1]
> 
http://www.boost.org/doc/libs/1_64_0/more/getting_started/windows.html#header-only-libraries

Zitat von dort:
"Most Boost libraries are header-only: they consist entirely of header 
files containing templates and inline functions"

Inline habe ich oben genannt, und Templates sind ein ganz anderer Fall. 
Die müssen im Header implementiert sein, damit sie funktionieren.

von Oliver S. (oliverso)


Lesenswert?

Rolf M. schrieb:
> Es verletzt die "one definition rule", es sei denn, man markiert die
> Funktionen explizit als "inline" oder als "static".

Da der TO das genau wegen inlining machen will, ist es eigentlich ganz 
sinnvoll, da dann auch inline davor zu schreiben...

Und wenns dann tatsächlich um C++ und Memberfunktionen geht, die per 
default inline, wenn man sie gleich in die Klassendeklaration schreibt, 
auch für nicht-Templates.

Oliver

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Janush B. schrieb:

> Dafür scheint das Konzept einer weiteren cpp und headerdatei Standard zu
> sein...? Dadurch muss man jede neue Funktion auch in der Headerdatei
> angeben und durch das getrennte kompilieren der main.cpp und
> funktionen.cpp funktionieren inline Funktionen vermutlich auch nicht
> mehr...?

Üblicherweise deklarierst Du nur die Funktionen in einem header, die Du 
auch aus der entsprechenden Implementierungsdatei exportieren möchtest. 
Sprich: Nur die Funktionen, die von ausserhalb dieser 
Implementierungsdatei aufrufen möchtest. Alle anderen Funktionen kannst 
Du mit `static` deklarieren.

Inlinen kann der Linker das am Ende immer noch. Bzw. der Compiler kann 
das natürlich immer noch mit den Funktionen, innerhalb einer 
Implementierungsdatei (translation unit) tun.

Aber: gerade Berufsanfänger überschätzen die Wirkung von `inline`. Ich 
würde auf inline verzichten und am Ende mit einem Profiler messen, wo 
Optimierungen am meisten Effekt haben.

Wenn Du den unbedingt die Wurzel allen Übels in Deinen Code streuen 
möchtest, kannst Du auch inline Funktionen im header implementieren.

> Daher wäre es mir eigentlich lieber, dass der ausgelagerte Code so
> kompiliert wird, als wäre alles in der main Datei.

Das möchtest Du aber nur so lange, so lange Dein Projekt sehr klein 
bleibt. Ansonsten muss der Compiler bei jeder kleinsten Änderung Dein 
komplettes Projekt übersetzen. Ich kenne da so ein Projekt, da dauert 
dann der Build immer 10 Minuten, egal wie klein die Änderung war.

> Die Trennung soll
> also eigentlich nur mir zur Übersicht dienen. Das ganze Konstrukt mit
> header usw finde ich eher umständlich gibt es da nicht eine "bessere"
> Möglichkeit?

Nein, es gibt z.Z. keine bessere Möglichkeit. Es gibt einige 
Bestrebungen, Modules in C++ zu implementieren.

Ansonsten sind header auch ein wunderbarer Ort, eine Funktion ausgibig 
zu Dokumentieren. Wenn ich Software designe, fange ich in der Regel mit 
den headern an und dokumentiere Klassen und Funktionen, bis ich mir 
sicher bin, dass das so funktionieren wird.

mfg Torsten

von Wilhelm M. (wimalopaan)


Lesenswert?

Torsten R. schrieb:
> Janush B. schrieb:
>
>> Dafür scheint das Konzept einer weiteren cpp und headerdatei Standard zu
>> sein...? Dadurch muss man jede neue Funktion auch in der Headerdatei
>> angeben und durch das getrennte kompilieren der main.cpp und
>> funktionen.cpp funktionieren inline Funktionen vermutlich auch nicht
>> mehr...?
>
> Üblicherweise deklarierst Du nur die Funktionen in einem header, die Du
> auch aus der entsprechenden Implementierungsdatei exportieren möchtest.
> Sprich: Nur die Funktionen, die von ausserhalb dieser
> Implementierungsdatei aufrufen möchtest. Alle anderen Funktionen kannst
> Du mit `static` deklarieren.
>
> Inlinen kann der Linker das am Ende immer noch. Bzw. der Compiler kann
> das natürlich immer noch mit den Funktionen, innerhalb einer
> Implementierungsdatei (translation unit) tun.

Die Bedeutung von inline wird oft missverstanden: inline heisst nicht 
notwendigerweise, dass der Compiler diese Funktion ohne "call" in den 
Code einbaut. Diese Bedeutung von inline ist nur ein Compile-Hint.

inline bedeutet im engeren Sinn, dass des mehrere(!) Definitionen in 
unterschiedlichen(!) Übersetzungseinheiten geben darf, ohne das es zu 
einer Verletzung der ODR kommt.

von Sven B. (scummos)


Lesenswert?

Sheeva P. schrieb:
> Sven B. schrieb:
>> Kannst du machen, ist aber keine so tolle Idee; sobald der Header
>> mehrfach benutzt wird, ist dann der Linker unglücklich. Die
>> Include-Guards helfen dagegen auch nicht.
>
> Wie das? Bitte zeig' doch mal einen Anwendungsfall. Bis dahin bleibe ich
> bei meiner Überzeugung, daß alle standardkompatiblen Compiler,
> Präprozessor und Linker damit vollkommen glücklich sind.

Beispiel steht ja schon oben ... du brauchst nur mehrere compilation 
units.

>> Lieber den Code in die cpp-Datei schreiben und nur die Definition
>> in den Header.
>
> Die Boost-Entwickler liefern die meisten ihrer Libs "header-only" aus
> [1]. Haben die keine Ahnung?

Bei boost ist halt quasi alles ein Template, dann hast du eh keine 
andere Wahl mehr. Die Regeln für Template- und Inline-Funktionen sind 
aber anders, ja.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.