Von std::string erben
-
@_ro_ro Wenn du was lernen möchtest, solltest du lernen, nicht von Klassen zu erben, die keinen virtuellen Destruktor haben.
std::string* base = new mystring("It's a string"); delete base;
Mit einem virtuellen Destruktor würde der Destruktor von mystring aufgerufen, ohne hast du keine Chance über den Pointer auf die Basisklasse dein Objekt aufzuräumen.
-
bisher habe ich gelernt,
new
nicht zu verwenden. Und was ist jetzt ein virtueller Destruktor?MFG
-
-
Man erbt nicht von den std::-Klassen. Siehe u.a. https://quuxplusone.github.io/blog/2018/12/11/dont-inherit-from-std-types/
-
Manchmal muss man eben Objekte im Freispeicher erzeugen. Ob das mit
new
oderstd::make_unique
geschieht, ist zwar für das Freigeben relevant, aber nicht für diese Fragestellung. Das Problem existiert also auch, ohne dass du explizit new rufst.
-
-
-
@_ro_ro Ein virtueller Destruktor ist ein Desturkor, mit dem Keyword
virtual
. Virtuelle Funktionen generell überschreiben die Funktion des Basisklasse, auch wenn die über einen Pointer auf die Basisklasse aufgerufen werden.using namespace std; class base { public: virtual ~base() = default; virtual void output() { cout << " base\n"; } }; class inhereited : public base { public: void output() override { cout << "inherited\n"; } }; int main(int, char const**) { base* base = new inhereited(); base->output(); delete base; return 0; }
Im Falle eines Destruktors gilt im Prinzip dasselbe, nur das sicher gestellt wird, das auch die Base aufgeräumt wird.
Das
new
verwende ich nur um schnell ein Beispiel zu haben, in realem Code wäre das ein Smartpointer, was aber am Problem nichts ändert.
-
-
-
@_ro_ro Rentner-Cop?
Bei Vererbung macht man sich unter anderem die Eigenschaft zu nutze, dass man den internen Zustand des "Elternobjekts" in der abgeleiteten Klasse ändern darf...
-
@nameName sagte in Von std::string erben:
@_ro_ro Rentner-Cop?
Bei Vererbung macht man sich unter anderem die Eigenschaft zu nutze, dass man den internen Zustand des "Elternobjekts" in der abgeleiteten Klasse ändern darf...
Bei Vererbung wird kein Elternobjekt erstellt sondern eine Instanz der erbenden Klasse.
MFG
-
@_ro_ro sagte in Von std::string erben:
@nameName sagte in Von std::string erben:
@_ro_ro Rentner-Cop?
Bei Vererbung macht man sich unter anderem die Eigenschaft zu nutze, dass man den internen Zustand des "Elternobjekts" in der abgeleiteten Klasse ändern darf...
Bei Vererbung wird kein Elternobjekt erstellt sondern eine Instanz der erbenden Klasse.
MFG
Ähm... nein. Beim Erstellen des konkreten Objekts werden von alle geerbten Klassen Instanzen erstellt, sie sind halt nur Teil des konkreten Objekts.
-
@DocShoe sagte in Von std::string erben:
@_ro_ro sagte in Von std::string erben:
@nameName sagte in Von std::string erben:
@_ro_ro Rentner-Cop?
Bei Vererbung macht man sich unter anderem die Eigenschaft zu nutze, dass man den internen Zustand des "Elternobjekts" in der abgeleiteten Klasse ändern darf...
Bei Vererbung wird kein Elternobjekt erstellt sondern eine Instanz der erbenden Klasse.
MFG
Ähm... nein. Beim Erstellen des konkreten Objekts werden von alle geerbten Klassen Instanzen erstellt, sie sind halt nur Teil des konkreten Objekts.
Nur Wenn der Elternkonstruktor aufgerufen wurde oder ist das grundsätzlich so?
MFG
-
@_ro_ro
Grundsätzlich und immer. Der Konstruktor wird immer benötigt, um alle Member einer Klasse zu initialisieren (selbst wenn sie default-constructed sind). Wenn du den Konstruktor einer Basisklasse nicht explizit selbst aufrufst ( durch die : Notation) erzeugt der Compiler einen impliziten Konstruktoraufruf des jeweiligen Defaultkonstruktors (ohne Parameter).Edit:
Ab C++11 kann man die Member direkt in der Deklaration der Klasse initialisieren, das musste man früher im Konstruktor machen. Wenn dann die Konstruktor ender jeweiligen Basisklassen nicht aufgerufen würde, wären die Member höchstens Default-konstruiert, aber nicht mit den Werten, mit denen sie im Konstruktor initialisiert werden.
-
danke Dir!! Eigentlich logisch, denn der Destruktor der Elternklasse(n) wird ja immer aufgerufen.
MFG
-
Nicht zwingend. Mann kann auch Käse programmieren, nehmen wir mal dein Anliegen und benutzen das absichtlich falsch:
#include <string> #include <memory> class MyString : public std::string { // hier liegt iwas auf dem Heap, das explizit aufgeräumt werden muss. public: MyString() = default; }; int main() { // unique_ptr hält einen Zeiger auf die Basisklasse! std::unique_ptr<std::string> = std::make_unique<MyString>(); }
Führt zu einem Problem, weil der Destruktor von
MyString
nicht aufgerufen wird. Das liegt daran, dass der Destruktor vonstd::string
nichtvirtual
ist und daher den Destruktor der Elternklasse nicht aufruft.
Deswegen sollte man auch nicht von Klassen erben, die keinen virtuellen Destruktor haben.Und wo wir bei dem Ursprungsproblem sind:
Wirf mal einen Blick auf boost Tokenizer. Die boost-Bibliotheken sind frei und für fast jeden C++ Compiler verfügbar. Die Installation kann etwas umständlich sein, wenn man sie selbst bauen muss, weil es keine Prebuilt Distro für deinen Compiler gibt.
-
...oder auf sowas wie https://abseil.io/docs/cpp/guides/strings#abslstrsplit-for-splitting-strings
Boost ist zwar schön, ich finde es aber oft recht kompliziert zu benutzen (nicht, dass abseil nicht auch seine eigenen Probleme hätte...)
-
Wie lange darf der Troll @_ro_ro hier noch posten?
-
Vererbung ist nicht immer sinnvoll. Erfahrungsgemäß ist Vererbung genau dann zweckmäßig, wenn Objekte um bestimmte Methoden erweitert werden oder gegebene Methoden spezialisiert werden sollen wobei die geerbten Methoden natürlich auch verwendet werden.
Insofern ist die Idee, das
std::string
Objekt um eine Methodesplit
zu erweitern alles Andere als abwegig. Zumal 1. das Splitten ohnehin nur auf Strings angewendet wird und 2. sehr oft benötigt wird. Genau das ist der Sinn einer objektorientierten Programmierung.Viele Grüße!
PS: Meine Splitfunktion (classless) funktioniert einwandfrei. Auch mit UTF-8-kodierten Zeichenketten.
-
@_ro_ro Generell ist mein Eindruck, das man versucht man in C++ Laufzeitpolymorphie zu vermeiden.
Tatsächlich benötigt man es selten, denn meistens ist vorher bekannt, welche Funktion tatsächlich ausgeführt werden soll. Und dafür möchte man den Performanceverlust durch die Indirektion über den v-table vermeiden.In C++ gibt es verschiedene Möglichkeiten für "Compile Time Polymorphism", umgesetzt z.B. über Templates. Da wird direkt zu Compiletime entschieden, welche Funktion aufgerufen werden soll. Ein Stichwort zur Suche ist z.B. CRTP.
Wenn ich mir überlege, dass ich zum Beispiel einen Split auf einem string brauche, hat deine Implementierung z.B: schon das Problem, dass sie nur auf
std::string
alsostd::base_string<char>
funktioniert und nicht aufstd::wstring
und anderen Varianten (https://en.cppreference.com/w/cpp/string/basic_string).Vielleicht wäre es sogar möglich einen Algorithmus zu schreiben, der so eine Art Split auf ganz unterschiedlichen Containern durchführen kann? Warum z.B. soll man einen Vektor nicht an einer bestimmten Stelle splitten können?
Mit C++20 (auch schon 4 Jahre alt) haben wir zum Beispiel
std::ranges::views::split
(https://en.cppreference.com/w/cpp/ranges/split_view) was das lösen soll und mit C++ 23 ist es sogar für Strings sinnvoll nutzbar#include <ranges> #include <iostream> #include <algorithm> #include <string_view> int main() { std::string a = "hallo welt"; for (const auto word : std::views::split(a, std::string(" "))) std::cout << std::string_view(word) << "\n"; return 0; }
geklaut und angepasst von hier (https://en.cppreference.com/w/cpp/ranges/split_view)
Zum spielen mit dem Code, siehe: https://godbolt.org/z/n5MsY5e7W
-
ungemein wichtiger ist es, daß alle diese Stringfunktionen zwischen bytesemantics und charactersemantics unterscheiden müssen: Wegen UTF-8. Also daß bspw. ein
length("ä")
bytesemantisch 2 ergibt, zeichenorientiert hingegen 1 sofern dieses 'ä' utf-8-kodiert ist. Denn sonst führt einsubstr()
u.U. dazu, daß die Einzelbytes von Zeichen ungewollt voneinander getrennt werden.Wie geht denn C++ mit diesem Sachverhalt um?
MFG
-
@_ro_ro sagte in Von std::string erben:
Wie geht denn C++ mit diesem Sachverhalt um?
Gar nicht.
Musst du selber machen oder darauf spezialisierte Bibliotheken verwenden. Zum Beispiel https://github.com/copperspice/cs_string
(Es gibt wstring, aber das hat ja eigentlich dasselbe Problem)
(Du kannst mitstream.imbue
arbeiten, aber das erschien mit immer unendlich kompliziert, schau dir dazu https://en.cppreference.com/w/cpp/locale/codecvt an - teilweise eingeführt mit C++11 und mit C++20 wieder deprecated... also besser lieber gar nicht aufmachen, dieses Fass)