C++: Ein paar Fragen Fragezeichen)



  • Hallo Community,

    ich habe mal ein paar "blöde" Frage, zu C++ .. ich entwickle gerade ein kleines Tool , und komme eigentlich ganz gut zurecht. Allerdings hab ich ein paar Fragen:

    • Struct oder Class? Der "alleinige" Unterschied liegt ja darin, dass in Struct alle Member automatisch Public sind !? Und seit C++ kann man ja auch Methoden C-Toren etc. in ner Struct reinhauen. Dann frage ich mich trozdem, wann verwende ich was?

    • Impliziert der Copy-Konstruktor den Zuweisungsoperator? Ich habe eine Struktur/Klasse die einen Copy-Ctor hat, und mit is aufgefallen, dass die Instanz auch durch eine Zuweisung Kopiert wird.

    • Sollte man immer eine CPP zur Header haben? Ich komme aus der .NET Welt, und da gibt es ja die Differenzierung zwischen H und CPP nicht. Und habe auch aus Bequemlichkeit in manchen Klassen die Implementierung mit in die Header gemacht, so dass ich keine CPP Datei brauche:) Das ist bestimmt unsauber oder? Wieso macht man das denn noch so? Solange ich keine Lib bereitstelle müsste ich ja den Code nich in CPP und H trennen?

    Danke für Euer Feedback


  • Mod

    1. Korrekt. Entweder nimm immer class, oder wenn du traditionelle Wurzeln betonen möchtest, nimm struct um reine Datenklassen zu kennzeichnen, die keine Methoden haben.
    2. Nein, eigentlich im Gegenteil. Wenn du einen Copy-Konstruktor definierst, dann heißt das eigentlich, dass kein Zuweisungsoperator mehr automatisch erzeugt wird. Kannst du ein Beispiel zeigen?
    3. Jain. Es gibt reichlich Fälle, in denen man in C++ alles im Header hat. Meistens weil es Templates sind (und somit keine vollständige Definition), oder weil alles so kurz und einfach ist, dass man es inline macht (und somit kein Problem mit Mehrfachdefinitionen hat). Deine Frage scheint aber darauf abzuzielen, dass du richtige Definitionen in Headern haben möchtest. Das geht nicht. Beziehungsweise es geht so lange, wie dein Programm zufällig nur genau ein einziges Modul hat, das diesen Header benutzt. Was meistens nicht der Fall sein dürfte bei jedem halbwegs umfangreichen Programm. Ich vermute, dass deine Programme bisher noch nicht umfangreich genug waren, als dass du auf die daraus resultierenden Fehler gestoßen wärst. Es ist recht eindeutig, wenn du diese Fehler hast. Der Linker beschwert sich dann wegen mehrfacher Definitionen und sich weigert sich, ein Programm zu erstellen. Hättest du garantiert bemerkt.


  • @SeppJ sagte in C++: Ein paar Fragen Fragezeichen):

    1. Nein, eigentlich im Gegenteil. Wenn du einen Copy-Konstruktor definierst, dann heißt das eigentlich, dass kein Zuweisungsoperator mehr automatisch erzeugt wird. Kannst du ein Beispiel zeigen?

    Mmh? Ich kann mir das zwar nie merken, aber das sieht mir falsch aus:

    class A
    {
    public:
    	A(){}
            A(const A&) {}
    };
    
    int main() {
    	A a;
    	A b = a;
    	a = b;   // kein Fehler hier
    	return 0; 
    }
    

  • Mod

    Oh, stimmt, das ist ja technisch gesehen noch erlaubt. Aber deprecated.

    Dann sagen wir es so: Impliziert wird die Zuweisung nicht durch den Copy-Konstruktor. Normalerweise verhindert sogar eine Definition einer speziellen Memberfunktion die automatische Definition der anderen. Aber diese spezielle Kombination ist (noch) erlaubt. Die Zuweisung geht also trotz Copy-Konstruktor, nicht wegen.



  • @SeppJ sagte in C++: Ein paar Fragen Fragezeichen):

    (...) Ich vermute, dass deine Programme bisher noch nicht umfangreich genug waren, als dass du auf die daraus resultierenden Fehler gestoßen wärst. Es ist recht eindeutig, wenn du diese Fehler hast. Der Linker beschwert sich dann wegen mehrfacher Definitionen und sich weigert sich, ein Programm zu erstellen. Hättest du garantiert bemerkt.

    Er schreibt ja "Klassen" ("habe auch aus Bequemlichkeit in manchen Klassen die Implementierung mit in die Header gemacht"). Ich gehe mal davon aus dass er die Implementierung dann auch direkt in der Klassendefinition gemacht hat. Gleich wie man es halt in Java oder C# schreibt. Womit sie dann implizit inline ist und es keine ODR Verletzung gibt.

    Natürlich kann auch das Probleme verursachen, aber eher keine die man notwendigerweise gleich bemerkt. Nämlich einerseits längere Compilezeiten und andrerseits grosse Binaries durch zu viel Inlining.



  • Guten Morgen Männer:)

    Ja genau wie @Jockelx beschreibt, funktioniert der Zuweisung Operator mit dem Copy-Ctor:) Das hat mich gewundert.

    Und @hustbaer hat recht in seiner Annahme, habe ich viel Definition Inline gemacht, bzw. in manchen Klassen alles. Aber wieso werden dann Die Dlls /Binäris Größer? sind denn die Header demnach mehrfach in den Dll's hinterlegt??

    Dann ist mir noch eine Frage zu Namespaces eingefallen(Die ja in .NET stark gelebt werden). In .Net muss ja eine Klasse in einem Namespace definiert sein Klasse (Hoffe das ist richtig). In C++ ist das ja nicht unbedingt so, da muss man es explizit angeben. Haben in C++ die Namespaces denn die gleiche Bedeutung wie in .NET? Wie sollte man die Namepaces definieren, am besten wie die Projekt-Struktur (Folders-Hierachie)? Wie macht Ihr das?

    Danke:)



  • @SoIntMan sagte in C++: Ein paar Fragen Fragezeichen):

    Guten Morgen Männer:)

    Ja genau wie @Jockelx beschreibt, funktioniert der Zuweisung Operator mit dem Copy-Ctor:) Das hat mich gewundert.

    Ums noch Mal deutlich zu sagen: es kompiliert, funktionieren tut es vielleicht zufällig, weil der Copy-Ctor bei dir wohl nichts spannendes macht.



  • Inline bedeutet, daß bei jedem Funktionsaufruf der komplette Code reinkompiliert wird (anstatt eines Funktionsaufrufs mit einmaligem Funktionscode). Bei mehrfacher Nutzung einer Funktion ist also der Code dafür auch mehrfach in der EXE/DLL vorhanden.
    Üblicherweise werden nur kurze Funktionsblöcke inline gemacht (wie z.B. einfache Getter, Setter, ...).

    Insbesondere für eigene Projekte sollte man auch in C++ einen eigenen Namespace benutzen (besonders natürlich, wenn dieser Code dann als Bibliothek o.ä. weitergegeben wird), ansonsten wird der globale Namensraum "vollgemüllt" (und führt evtl. zu Fehlermeldungen wegen doppelter Definition).
    Und bei größeren Projekten sind Sub-Namespaces sinnvoll (wie in .NET auch).
    Nur die main()-Funktion darf in C++ nicht in einem eigenen Namensraum definiert sein (sonst findet der Linker sie nicht).



  • @SoIntMan sagte in C++: Ein paar Fragen Fragezeichen):

    Ja genau wie @Jockelx beschreibt, funktioniert der Zuweisung Operator mit dem Copy-Ctor:) Das hat mich gewundert.

    Ich glaube du hast da was falsch verstanden. Wenn man den Copy-Ctor selbst definiert, dann normalerweise deswegen, weil der automatisch generierte nicht das macht was man braucht. In dem Fall sollte man auch den Zuweisungsoperator definieren, denn wenn der automatisch generierte Copy-Ctor nicht das macht was man braucht, dann wird der automatisch generierte Zuweisungsoperator im Normalfall auch nicht das machen was man braucht.

    Nur nochmal damit es wirklich klar ist. In der Zeile a = b; im Beispiel von @Jockelx wird der automatisch generierte Zuweisungsoperator verwendet, und was der macht hat überhaupt nichts mit deinem Copy-Ctor zu tun. Nichts. Der macht genau das selbe wie wenn du überhaupt keinen Copy-Ctor definiert hättest. Und daher vermutlich das falsche.

    Natürlich gibt es Fälle wo man im Copy-Ctor etwas spezielles machen muss, im Zuweisungsoperator aber nicht. z.B. wenn man nur mitzählen will wie viele Objekte man hat. In dem Fall müsste man nur in jedem Ctor "counter++" machen und im Dtor "counter--", der Zuweisungsoperator kann einem aber egal sein da er an der Anzahl der Objekte nichts ändert. Sowas sind aber eher Sonderfälle.

    viel Definition Inline gemacht, bzw. in manchen Klassen alles. Aber wieso werden dann Die Dlls /Binäris Größer? sind denn die Header demnach mehrfach in den Dll's hinterlegt??

    Sie müssen nicht notwendigerweise grösser werden, aber sie können, und sie tun oft. Der Grund ist dass die meisten Compiler, mit den üblichen Einstellungen, nur Inlining von Funktionen machen die sie während der Übersetzung eines .cpp Files sehen. Und sehen können sie nur was im Header File steht, nicht aber was in anderen .cpp Files des selben Projekts steht. Und mehr Inlining heisst halt oft auch grössere Binaries. Bei wirklich kleinen Funktionen macht es oft Sinn sie ins Header File zu schreiben, damit sie inlined werden können. Weil es zu schnellerem und sogar oft zu kleinerem Code führt. Bei grösseren Funktionen, speziell bei welchen die sowieso schon >= 100 Zyklen brauchen, macht es dagegen seltener Sinn sie zu inlinen. Der Compiler kann das aber nur auf Grund von dem entscheiden was er sieht, und sehen tut er immer nur das eine .cpp File das er gerade kompiliert. Wenn da dann eine "mittelgrosse" Funktion nur an einer Stelle aufgerufen wird, entscheidet der Compiler oft sie zu inlinen. Trotz dem sie vielleicht 2x oder auch 10x so gross ist wie der Funktionsaufruf. Und wenn das in 10 verschiedenen .cpp Files passiert, dann hast du den Code für diese Funktion halt 10x in deinem Programm - statt 1x.

    Ebenso muss der Compiler die Funktion dann min. 10x parsen und 10x Code dafür generieren. Was Zeit braucht, und damit die Übersetzung verlangsamt. Weiters führt das Inline Implementieren sehr oft dazu dass man im Header File mehr andere Headers einbinden muss, als wenn die Funktion nicht dort implementiert wäre. Was nochmal die Übersetzungszeit in die Höhe treibt.

    Bei kleinen bis mittelgrossen Projekten, speziell wenn die grösse der Binaries nicht so wichtig ist, kann man das schonmal ignorieren. Bei grossen Projekten kann das aber ordentlich reinhauen. In der Software an der ich arbeite haben wir etliche Files die mehr als 10 Sekunden zum Übersetzen brauchen. Bei einem Projekt das aus > 100 Binaries besteht, wovon jedes aus zig bis hunderten .cpp Files gebaut wird, ... naja kannst dir ja einfach selbst ausrechnen was das bedeutet.



  • @SoIntMan sagte in C++: Ein paar Fragen Fragezeichen):

    Haben in C++ die Namespaces denn die gleiche Bedeutung wie in .NET?

    Jain. Sinn und Zweck von Namespaces in C++ und C# sind grundsätzlich gleich. using namespace funktioniert in C++ allerdings etwas anderes. Wenn du in C++ using namespace X machst, dann kannst du danach auf X::Y::Z mit Y::Z zugreifen. In C# müsstest du weiterhin X.Y.Z schreiben, da du ja bloss X reingeholt hast, nicht aber X.Y.Z.

    Und was noch dazukommt: In C++ macht man gerne mal Templates und auch gerne mal freie Funktionen. Und mit freien Funktionstemplates kann man schnell Uneindeutigkeiten bekommen wenn man zuviel mit using namespace reinzieht. Bzw. im Worst-Case findet er zwei Funktionen, und wählt dann die falsche aus, weil sie dummerweise laut der Overload-Resolution Regeln "besser passt".

    Insgesamt ist using namespace auf jeden Fall in C++ viel schneller problematisch als in C#. Weswegen es in C++ auch viel unüblicher ist. Was dazu führt dass man oft sämtliche Namespaces angeben muss. Was wiederrum dazu führt dass viele C++ Projekte eher kurze Bezeichner für Namespaces verwenden, und auch eher flache Namespace-Hierarchien, damit nicht jede zweite Zeile zu 90% aus Namespace-Würsten besteht.



  • Hallo:) Echt Super erklärt Danke:) Das Hilft mir schon sehr viel weiter:)

    Muss das alles aber mal im "Doing" verstehen:)

    Noch eine ganz andere Frage, wenn schon alle warm sind:p

    Welche Logging Lib verwendet man in C++ üblicherweisse.. in .NET habe ich immer NLog verwendet. Google meinte spdLog sei gut.. aber da finde ich keine libs. Jemand einen Tip?



  • @SoIntMan sagte in C++: Ein paar Fragen Fragezeichen):

    Google meinte spdLog sei gut.. aber da finde ich keine libs

    Ist doch leicht zu finden:
    https://github.com/gabime/spdlog

    Was meinst du mit "keine lib"? spdlog ist, wie im README steht, "Very fast, header only, C++ logging library." (Hervorhebung von mir)


Anmelden zum Antworten