Übung 1: String-Klasse



Ähnliche Dokumente
Arbeiten mit UMLed und Delphi

Zählen von Objekten einer bestimmten Klasse

Vorkurs C++ Programmierung

Übungen zu Einführung in die Informatik: Programmierung und Software-Entwicklung: Lösungsvorschlag

Einführung zum Arbeiten mit Microsoft Visual C Express Edition

Objektorientierte Programmierung

Ziel, Inhalt. Programmieren in C++ Wir lernen wie man Funktionen oder Klassen einmal schreibt, so dass sie für verschiedene Datentypen verwendbar sind

Menü Macro. WinIBW2-Macros unter Windows7? Macros aufnehmen

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Einführung in die Programmierung

Windows. Workshop Internet-Explorer: Arbeiten mit Favoriten, Teil 1

Einführung in die Programmierung (EPR)

Starten Sie Eclipse: Hier tragen sie Ihr Arbeitsverzeichnis ein. Zu Hause z.b. c:\workspace.

Stundenerfassung Version 1.8 Anleitung Arbeiten mit Replikaten

Gruppenrichtlinien und Softwareverteilung

Zwischenablage (Bilder, Texte,...)

Einführung in die Programmierung

Einführung in die Java- Programmierung

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Datensicherung. Beschreibung der Datensicherung

FuxMedia Programm im Netzwerk einrichten am Beispiel von Windows 7

4 Aufzählungen und Listen erstellen

4. BEZIEHUNGEN ZWISCHEN TABELLEN

2. Semester, 2. Prüfung, Lösung

Anleitung über den Umgang mit Schildern

Punkt 1 bis 11: -Anmeldung bei Schlecker und 1-8 -Herunterladen der Software

Abschluss Version 1.0

Das sogenannte Beamen ist auch in EEP möglich ohne das Zusatzprogramm Beamer. Zwar etwas umständlicher aber es funktioniert

Installation und Inbetriebnahme von Microsoft Visual C Express

Folge 18 - Vererbung

Informationen zur Verwendung von Visual Studio und cmake

Handbuch ECDL 2003 Basic Modul 5: Datenbank Access starten und neue Datenbank anlegen

Wie halte ich Ordnung auf meiner Festplatte?

AutoCAD Dienstprogramm zur Lizenzübertragung

Java: Vererbung. Teil 3: super()

Software-Engineering und Optimierungsanwendungen in der Thermodynamik

Ordnung auf/im (Win-)PC bzw. der Festplatte

Objektorientierte Programmierung mit C++ Vector und List

2. Im Admin Bereich drücken Sie bitte auf den roten Button Webseite bearbeiten, sodass Sie in den Bearbeitungsbereich Ihrer Homepage gelangen.

5 DATEN Variablen. Variablen können beliebige Werte zugewiesen und im Gegensatz zu

TESTEN SIE IHR KÖNNEN UND GEWINNEN SIE!

Lösungsvorschlag zum Übungsblatt 1 zur Vorlesung Informatik II / WS2001/02

Der neue persönliche Bereich/die CommSy-Leiste

Wenn wir also versuchen auf einen anderen PC zuzugreifen, dann können wir sowohl per Name als auch mit der Adresse suchen.

Objektorientierte Programmierung. Kapitel 12: Interfaces

Erstellen der Barcode-Etiketten:

Es sollte die MS-DOS Eingabeaufforderung starten. Geben Sie nun den Befehl javac ein.

Wordpress: Blogbeiträge richtig löschen, archivieren und weiterleiten

1 Einleitung. Lernziele. automatische Antworten bei Abwesenheit senden. Einstellungen für automatische Antworten Lerndauer. 4 Minuten.

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

L10N-Manager 3. Netzwerktreffen der Hochschulübersetzer/i nnen Mannheim 10. Mai 2016

Bereich METIS (Texte im Internet) Zählmarkenrecherche

Internationales Altkatholisches Laienforum

HANDBUCH PHOENIX II - DOKUMENTENVERWALTUNG

Flash Videos einbinden

Downloadfehler in DEHSt-VPSMail. Workaround zum Umgang mit einem Downloadfehler

5. Tutorium zu Programmieren

teamsync Kurzanleitung

MORE Profile. Pass- und Lizenzverwaltungssystem. Stand: MORE Projects GmbH

Hilfen zur Verwendung der Word-Dokumentvorlage des BIS-Verlags

Anleitung Postfachsystem Inhalt

Erstellen einer digitalen Signatur für Adobe-Formulare

Mediator 9 - Lernprogramm

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

Speicher in der Cloud

Benutzerhandbuch DesignMaster II

CodeSaver. Vorwort. Seite 1 von 6

State Machine Workflow mit InfoPath Formularen für SharePoint 2010 Teil 6

M. Graefenhan Übungen zu C. Blatt 3. Musterlösung

Klausur in Programmieren

Dokumentenverwaltung

Das DAAD-PORTAL. Prozess der Antragstellung in dem SAPbasierten Bewerbungsportal des DAAD.

Anleitung zur Daten zur Datensicherung und Datenrücksicherung. Datensicherung

Historical Viewer. zu ETC5000 Benutzerhandbuch 312/15

1. Adressen für den Serienversand (Briefe Katalogdruck Werbung/Anfrage ) auswählen. Die Auswahl kann gespeichert werden.

Windows 7 Ordner und Dateien in die Taskleiste einfügen

Universal Gleismauer Set von SB4 mit Tauschtextur u. integrierten Gleismauerabschlüssen!

Nicht kopieren. Der neue Report von: Stefan Ploberger. 1. Ausgabe 2003

P&P Software - Adressexport an Outlook 05/29/16 14:44:26

Animationen erstellen

Q & A: Representation Tool

Institut für Programmierung und Reaktive Systeme 26. April Programmieren II. 10. Übungsblatt

Step by Step Softwareverteilung unter Novell. von Christian Bartl

Medea3 Print-Client (m3_print)

! " # $ " % & Nicki Wruck worldwidewruck

1. Einführung Erstellung einer Teillieferung Erstellung einer Teilrechnung 6

Hex Datei mit Atmel Studio 6 erstellen

Anleitung zur Verwendung der VVW-Word-Vorlagen

1 Einleitung. Lernziele. Symbolleiste für den Schnellzugriff anpassen. Notizenseiten drucken. eine Präsentation abwärtskompatibel speichern

Jederzeit Ordnung halten

Lernerfolge sichern - Ein wichtiger Beitrag zu mehr Motivation

Was meinen die Leute eigentlich mit: Grexit?

Schrittweise Anleitung zur Erstellung einer Angebotseite 1. In Ihrem Dashboard klicken Sie auf Neu anlegen, um eine neue Seite zu erstellen.

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf Seite 1 von 22

Grundlagen von Python

Hochschule Aalen. OpenVPN. Anleitung zur Installation

Primzahlen und RSA-Verschlüsselung

Flyer, Sharepics usw. mit LibreOffice oder OpenOffice erstellen

Dateimanagement in Moodle Eine Schritt-für

Transkript:

Programmieren in C++ Prof. Dr. Christoph Stamm, Prof. Horst Veitschegger Übung 1: String-Klasse Lernziele Sie repetieren den Stoff aus der Vorlesung: Struktur eines C++-Programms, einfache und strukturierte Datentypen, Zeiger, Referenzen, Arrays, C-Strings und Klassen. Sie entwickeln eine eigene String-Klasse für nicht veränderbare Zeichenketten ähnlich der Klasse String aus Java. Sie schreiben eine kleine Konsolenanwendung und verwenden dabei Ihre String-Klasse. Sie testen Ihre String-Klasse mit Unit-Tests. Sie verstehen das Potential der Move-Semantik und die Auswirkungen auf die Performance. 1 Tutorial und Aufgaben 1.1 C-Strings und String-Funktionen Ein C-String ist ein Character-Array, von dem nur die Anfangsadresse bekannt ist. Die Länge des Arrays ist statisch, jedoch nicht explizit im Array festgehalten. Um dennoch unterschiedlich lange Zeichenketten in einem solchen C-String abspeichern zu können, wird das Ende der Zeichenkette durch ein '\0' Zeichen gekennzeichnet. Solche C-Strings werden von C/C++ direkt unterstützt. Für den Umgang mit ihnen gibt es eine Reihe von globalen String-Funktionen in der C-Standardbibliothek (<cstring>). Eine Bündelung der String-Funktionen mit der Datenstruktur zu einer Klasse String macht also Sinn. Selbstverständlich gibt es in der C++-Standardbibliothek (<string>) bereits eine solche Klasse. Diese Strings sind veränderbar und entsprechen in etwa der Klasse StringBuilder aus Java. 1.2 String-Klasse In dieser Übung sollen Sie schrittweise eine eigene String-Klasse für unveränderbare Strings entwickeln und diese eingehend testen. Diese Klasse soll also im Wesentlichen der Klasse String aus Java entsprechen. Dabei lernen Sie verschiedenste (neue) C++-Techniken, den Debugger und das Unit- Testsystem von Visual Studio 1 kennen. Eröffnen Sie ein leeres C++ Konsolenprojekt mit einem eigenen Verzeichnis für die Solution. Der Solution werden wir später noch ein Testprojekt hinzufügen. Fügen Sie dem Konsolenprojekt dann drei Dateien hinzu: MyString.h, MyString.cpp und main.cpp 2. Während die beiden ersten Dateien für die Spezifikation und die Implementierung der String-Klasse dienen, nutzen wir main.cpp für eine erste kleine Konsolenanwendung, um ein paar einfache Tests (vorerst noch ohne Unit-Test) auf der Konsole auszuführen. Definieren Sie in der Datei MyString.h eine eigene String-Klasse. Wenn Sie hinter dem Klassennamen final hinschreiben, dann kann die Klasse nicht als Basisklasse verwendet werden. class String final { // private Datenstruktur public: // öffentliche Methoden ; 1 Die hier gemachten Beschreibungen beziehen sich auf Visual Studio 2012. Sie müssen nicht unbedingt mit Visual Studio arbeiten. Sie benötigen jedoch ein Werkzeug, welches Unit-Tests und Code- Coverage-Analysis ermöglicht. Zudem ist es sinnvoll, dass Sie in Ihrer Informatikausbildung noch andere IDEs ausser Eclipse kennenlernen. 2 Ihre Klasse soll schlicht und einfach String heissen. Um Verwechslungen mit vorhandenen Header- Files zu vermeiden, verwenden wir für die Dateien den Namen MyString. In C++ muss die Datei nicht den Namen der Klasse tragen. 1

1.2.1 Datenstruktur Unsere String-Datenstruktur soll den Java-Strings konzeptionell entsprechen. Das heisst, sie soll ein nicht veränderbares Character-Array (m_string) verwalten. Im Unterschied zu Java verwenden wir hier den 8-Bit Datentyp char, wobei es ohne weiteres möglich wäre, diesen durch den 16-Bit Datentyp wchar_t zu ersetzen. Zur Speicherung des Character-Arrays könnte ein C++-Container verwendet werden. Um den Umgang mit Arrays und Zeigerobjekten zu üben, verwenden wir hier ein herkömmliches, nicht null-terminiertes C-Array. Bekanntlich speichern C-Arrays die Arraylänge nicht im Array. Daher müssen wir in unserer Datenstruktur ein Längenfeld m_len vom Typ size_t 3 vorsehen. Ausgelegt auf eine effiziente Implementierung der Methode substring speichern wir noch ein drittes Attribut ab, nämlich die Startposition m_start des Strings. Damit können wir auf das jeweilige Anlegen eines neuen Character-Arrays verzichten und stattdessen das bereits vorhandene Character-Array wiederverwenden und einfach angeben, an welcher Array-Position der Teilstring beginnt. Character-Arrays können in C/C++ ganz einfach durch einen rohen Zeiger vom Typ char* repräsentiert werden. Ein solcher Rohzeiger wäre hier aber mit grosser Vorsicht zu verwenden, weil mehrere String-Objekte auf den gleichen C-String verweisen dürfen, wie wir das eben für Teilstrings definiert haben. Hier bietet sich also für m_string eines der neuen Zeigerobjekte von C++11 bestens an: shared_ptr<char>. Dieses smarte Zeigerobjekt registriert, wenn der Zeiger kopiert wird. Dadurch wird sichergestellt, dass das Array erst dann vom Heap gelöscht wird, wenn kein String-Objekt mehr darauf verweist. 1.2.2 Konstruktoren und Destruktor Vorerst wollen wir drei verschiedene Konstruktoren anbieten: den Standardkonstruktor, den Kopierkonstruktor und einen Typkonvertierungs-Konstruktor, mit dem konstante C-Strings in Instanzen unserer String-Klasse umgewandelt werden können. Der Standardkonstruktor benötigt keine Argumente und repräsentiert einen leeren String (Länge = 0). Ein Character-Array muss daher nicht alloziert werden. Der Kopierkonstruktor soll eine flache Kopie erstellen, denn es kann problemlos das gleiche Character-Array wiederverwendet werden. Grundsätzlich könnte der vom System automatisch erstelle Kopierkonstruktor verwendet werden. Damit Sie besser verstehen, wann dieser Konstruktor aufgerufen wird und hinein debuggen können, empfehlen wir Ihnen, diesen selber zu programmieren und eventuell mit einer Konsolenausgabe zu versehen. Der Typkonvertierungs-Konstruktor nimmt einen herkömmlichen C-String vom Typ const char* entgegen und erzeugt eine tiefe Kopie, da unser String-Objekt unabhängig vom Übergabeparameter bleiben soll. Weil die Länge des übergebenen C-Strings oft nicht bekannt ist, muss zuerst der 0- Character gesucht werden. Achten Sie darauf, dass auch ein leerer C-String "" übergeben werden kann. Der kritische Punkt in diesem Konstruktor ist die korrekte Erzeugung des Zeigerobjekts shared_ptr<char> m_string. Dieses Zeigerobjekt verwaltet streng genommen nur einen Zeiger auf einen einzelnen Character und nicht auf ein ganzes Character-Array, denn der generische Typ des Zeigerobjekts ist char. Leider ist es nicht möglich, einen shared_ptr<char[]> (beachten Sie die eckigen Klammern hinter char) zu verwenden. Dieser Umstand wird spätestens beim automatischen Speicherabbau zum Problem, weil dann nur ein einzelner Character auf dem Heap freigegeben wird und nicht das ganze Array. Um dieses Problem zu lösen, kann dem Konstruktor von shared_ptr<> ein zweites Argument, ein sogenannter Deleter, mitgegeben werden, welcher dann für den korrekten Speicherabbau zuständig ist. Es gibt aber noch einen zweiten Lösungsansatz. Im Gegensatz zu shared_ptr<> kann unique_ptr<> mit C-Arrays korrekt verfahren, wenn der generische Typ des Zeigerobjekts ein Array-Typ ist. Die Anweisung unique_ptr<char[]>(new char[m_len]) erzeugt ein Character-Array auf dem Heap und das Zeigerobjekt stellt im Destruktor sicher, dass das ganze Array wieder vom Heap entfernt wird. Unser Attribut m_string soll aber vom Typ shared_ptr<char> sein. Das ist jedoch unproblematisch, weil der Konstruktor von shared_ptr<char> auch einen unique_ptr<char[]> als Parameter akzeptiert. Bleibt schliesslich noch der Destruktor. Was gibt es hier zu tun? Was wäre anders, wenn wir einen rohen Zeiger anstatt dem shared_ptr<char> für das Character-Array in unserer Datenstruktur verwendet hätten? Auch hier empfehlen wir Ihnen, diesen Destruktor aus didaktischen Gründen zu implementieren, damit Sie hineindebuggen und überprüfen können, ob wirklich das Character-Array freigegeben wird. 1.2.3 Methoden Bevor Sie die Methoden der Klasse implementieren, empfehlen wir Ihnen Kapitel 1.3 durchzuarbeiten. Dort entwickeln Sie eine kleine Konsolenanwendung, welche die String-Klasse verwendet. 3 size_t ist ein vorzeichenloser Typ für ganzzahlige Grössenangaben 2

Ihre String-Klasse soll die nachfolgenden Instanz- und Klassen-Methoden anbieten. Beachten Sie, dass alle Instanzmethoden ein const am Ende haben. Damit wird ausgedrückt, dass diese Methoden das this-objekt nicht verändern, also nur lesenden Zugriff haben. // Instanz-Methoden char charat(size_t index) const; // bei falschem Index: wirft exception int compareto(const String& s) const; // C++ untypische Funktion: gibt -1, 0, 1 zurück String concat(char c) const; // hängt c ans Ende und gibt neuen String zurück String concat(const String& s) const; size_t length() const; // gibt die Länge des Strings zurück // Substring des Bereichs [beg, end) // falls beg m_len oder end beg: gibt leeren String zurück String substring(size_t beg, size_t end) const; // erzeugt 0-terminierten C-String, kopiert Inhalt und gibt Zeigerobjekt zurück unique_ptr<char[]> tocstring() const; // Gleichheitsoperator (Inline-Implementation schon gegeben) bool operator==(const String& s) const { return compareto(s) == 0; // Ausgabe-Operator für Output-Streams (Inline-Implementation schon gegeben) friend ostream& operator<<(ostream& os, const String& s) { const size_t end = s.m_start + s.m_len; const char* const sc = s.m_string.get(); for(size_t i=s.m_start; i < end; i++) os << sc[i]; return os; // Klassen-Methode static String valueof(int i); // erzeugt eine String-Repräsentation von i Übernehmen Sie die obenstehenden Methoden-Spezifikationen in Ihre Klassendefinition (MyString.h) und implementieren Sie dann sorgfältig die einzelnen Methoden innerhalb von MyString.cpp. Die Methode compareto(const String& s) ist für C++ untypisch, weil üblicherweise die herkömmlichen relationalen Operatoren für den gleichen Zweck eingesetzt werden. Wie dies in der Praxis aussehen könnte, zeigt der Gleichheitsoperator dessen Implementierung mithilfe von compareto bereits gegeben ist. Der Rückgabewert von compareto ist wie folgt zu wählen: -1) der this-string liegt lexikografisch vor dem String s; 0) beide Strings sind gleich; 1) s liegt lexikografisch vor dem this-string. Die Methode tocstring soll das Gegenstück zum Typkonvertierungs-Konstruktor darstellen. Damit wir Funktionen, welche einen herkömmlichen C-String benötigen, auch mit unserer String-Klasse bedienen können, bieten wir die Methode tocstring an, welche einen 0-terminierten C-String erzeugt und zurückliefert. Eine mögliche Implementierung dieser Methode könnte wie folgt aussehen: unique_ptr<char[]> String::toCString() const { unique_ptr<char[]> r = unique_ptr<char[]>(new char[m_len + 1]); const char * const tc = m_string.get(); //for (size_t i=0; i < m_len; i++) r[i] = tc[m_start + i]; memcpy(r.get(), tc + m_start, m_len); r[m_len] = '\0'; return move(r); Um sicherzustellen, dass der Aufrufer dieser Methode nicht vergisst, den extra allozierten C-String freizugeben, geben wir ein Zeigerobjekt zurück, welches für den automatischen Speicherabbau sorgen wird: String s("abc"); { auto up = s.tocstring(); // hier wird der C-String auf dem Heap angelegt cout << up.get() << endl; // Ausgabe des C-Strings auf der Konsole // hier wird der C-String wieder vom Heap entfernt 1.3 Testprogramm Editieren Sie die Datei main.cpp in der nachfolgenden Art, führen Sie den Build-Prozess aus und starten Sie die Ausführung zuerst ohne und danach mit Debugger. Gehen Sie mit F11 in die einzelnen 3

Methoden hinein, um zu sehen, wie Ihre Konstruktoren ausgeführt werden. So lange keine Laufzeitfehler auftreten ist so weit alles ok. Natürlich sagt das noch nichts über die Korrektheit der Implementierung aus. Sobald Sie mehr Methoden der Klasse implementiert haben, sollten Sie dieses Testprogramm um die neuen Methoden bereichern. #include "stdafx.h" // diese Zeile nur, fall Ihr Projekt eine solche Datei enthält #include "MyString.h" // die hier verwende Klasse heisst String using namespace std; // wird evtl. erst später verwendet int main() { String s0; String s1(""); String s2("abc"); String s00(s0); String s11(s1); String s22(s2); 1.4 Unit-Tests In diesem Abschnitt lernen Sie, wie Sie in VS 2012 ein natives C++ Unit-Test-Projekt anlegen und ausführen können. Zur Erklärung gehen wir von folgender Dateistruktur aus: \Uebung1 - \MyString - \UnitTest Der Ordner Uebung1 enthält die Solution-Datei Uebung1.sln und der Ordner MyString das Projekt MyString.vcxproj und die entsprechenden Quellcode-Dateien. Der Unterordner UnitTest wird erst jetzt durch Erstellung eines neuen Projektes erzeugt. Mit Rechtsklick im Solution Explorer auf Solution Uebung1 können Sie Add/New Project auswählen. Wählen Sie dann unter Visual C++/Test das Native Unit Test Project aus. Ändern Sie den Projektnamen zu UnitTest und drücken Sie auf OK. Nun wird das Projekt im oben gezeigten Ordner mit der Testdatei unittest1.cpp automatisch angelegt. Editieren Sie die Testdatei wie nachfolgend. Beachten Sie bitte, dass vorerst MyString.h nicht bekannt ist, weil diese Datei in einem anderen Verzeichnis liegt. #include "stdafx.h" #include "CppUnitTest.h" #include "MyString.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace UnitTest { TEST_CLASS(MyString) { public: TEST_METHOD(Construction) { String s0; String s1(""); String s2("abc"); String s00(s0); String s11(s1); String s22(s2); TEST_METHOD(Length) { Assert::IsTrue(String().length() == 0); Assert::IsTrue(String("").length() == 0); Assert::IsTrue(String("abc").length() == 3); ; Damit Sie Ihre String-Klasse in diesem Unit-Testprojekt verwenden können, müssen Sie über die Projekteinstellungen dem Compiler und Linker die nachfolgenden Angaben machen: 4

Im oberen Dialog sagen Sie dem Compiler wo er die Include-Datei MyString.h findet und dem Linker, wo er die Library oder Object-Datei findet. Im unteren Dialog sagen Sie dem Linker, in welcher Kompilationseinheit (MyString.obj) sich der entsprechende Object-Code befindet. Entwickeln Sie nun verschiedene, sinnvolle Testmethoden analog zu TEST_METHOD(Construction), um Ihre Methoden zu testen. Mehr Informationen dazu finden Sie hier: http://msdn.microsoft.com/en-us/library/hh694604.aspx Die Code-Abdeckung Ihrer Tests können Sie mit dem Code-Coverage-Tool ( TEST\Analyze Code Coverage\All Tests ) überprüfen. Bei einer Abdeckung von nahezu 100% Ihrer String-Klasse wissen Sie, dass die Tests das überprüfen, was Sie programmiert haben. Das bedeutet aber noch lange nicht, dass der Code das tut, was er tun sollte. Um das herauszufinden, müssen die Tests nicht vom Programmierer, sondern vom Anwender oder Auftraggeber angefertigt werden. Aus diesem Grund werden Sie nach Abgabe Ihrer String-Klasse von uns einen Unit-Test erhalten, den Sie ausführen müssen. Falls unsere Tests nicht alle erfolgreich sind und die Code-Coverage ungenügend ist, so müssen Sie Ihren Code nochmals überarbeiten. Geben Sie den überarbeiteten Code mit den markierten Änderungen, zusammen mit einem Screenshot ab, wo die erfüllten Tests und die Code-Coverage- Results ersichtlich sind. 1.5 Verfeinerung der String-Klasse Kehren wir zu unserer String-Klasse zurück. Bis jetzt haben wir zwei wichtigen Aspekten noch zu wenig Beachtung geschenkt: der Unveränderbarkeit von String-Objekten und der Move-Semantik. Wenden wir uns zuerst der Unveränderbarkeit zu. Wir haben in unserem Klassen-Design vorgesehen, dass von String nicht abgeleitet werden kann und dass die Instanzmethoden nur lesenden Zugriff auf die Instanz haben. So weit so gut. Wie sieht es aber mit dem vom System automatisch angebotenen Zuweisungsoperator aus? Würde nicht gerade eine Zuweisung der Art String s1 = "abc", s2 = "def"; s1 = s2; die Unveränderbarkeit von s1 auf fundamentale Art verletzen? Bevor wir diese Frage aus C++-Sicht beantworten, betrachten wir zuerst was Java in genau dieser Situation tun würde. In Java sind s1 und s2 Referenzen auf String-Objekte, die auf dem Heap verwaltet werden. Ein String-Objekt selber enthält dann eine Referenz auf eine unveränderbare Zeichenkette (String-Literal). Dadurch ist es 5

möglich, dass ein mittels s1.substring() neu erzeugtes String-Objekt auf das gleiche String-Literal verweist, wie das von s1 referenzierte String-Objekt. Die Zuweisung s1 := s2 verändert an den beiden String-Objekten und somit auch an den beiden String-Literalen gar nichts. Die Referenz s1 zeigt nun einfach auch auf das von s2 referenzierte String-Objekt. Der Garbage-Collector kann dann das unreferenzierte String-Objekt vom Heap wegräumen. Ob das String-Literal selber entfernt werden kann, hängt davon ab, in welchem Speicher das String-Literal angelegt worden ist und ob es noch andere String-Objekte gibt, welche auf das gleiche String-Literal verweisen. Bei unserer C++ String-Klasse sieht das sehr ähnlich aus. Nur sind s1 und s2 keine Referenzen, sondern die eigentlichen String-Objekte, die auf dem Stack liegen. Die darin verwendeten Character- Arrays (entsprechen den String-Literalen in Java) liegen hingegen auf dem Heap. Die Zuweisung s1 := s2 verändert zwar das String-Objekt s1, indem eine flache Kopie von s2 erstellt wird, lässt aber das von s1 bisher verwendete Character-Array unverändert oder löscht es allenfalls vom Heap, falls es anderweitig nicht mehr gebraucht wird. Dieses automatische Aufräumen wird durch die Verwendung der smarten Zeigerobjekte erreicht. Das heisst, die Ausführung der Zuweisung ist zwar aus speichertechnischen Gründen unproblematisch, verletzt aber die Unveränderbarkeit des String-Objekts welche eventuell zu rigid formuliert worden ist, weil nur die Unveränderbarkeit des Character-Arrays notwendig ist. Möchte man trotzdem den Einsatz des Zuweisungsoperators verhindern, welche Möglichkeiten bieten sich dazu? Erarbeiten Sie dazu zwei mögliche Vorschläge aus. 1.5.1 Move-Semantik Die Move-Semantik ist vor allem für Objekte wichtig, bei denen der Kopierkonstruktor eine tiefe Kopie eines grossen Objektes anlegt. Das ist in unserer String-Klasse nicht der Fall. Dennoch kann die Move-Semantik helfen, überflüssige Kopien von temporären Objekten zu vermeiden. Das wollen wir an folgendem Beispiel ausprobieren: int main() { String s = String("ab").concat("cd").concat("ef"); cout << s << endl; Überlegen wir uns mal genau, welche String-Objekte mit welchen Konstruktoren erstellt und wann welche Objekte wieder gelöscht werden. Gehen wir vorerst davon aus, dass für unsere String-Klasse keine Move- Semantik zur Verfügung steht. String("ab") ruft offensichtlich den Typkonvertierungs-Konstruktor auf. Die Methode concat erwartet eine Referenz auf ein String-Objekt. Als Parameter übergeben wir aber einen C-String. Das bedeutet, das System ruft den Typkonvertierungs-Konstruktor je einmal implizit für "cd" und "ef" auf und erzeugt temporäre String-Objekte auf dem Stack. Nun kann String("ab").concat("cd") ausgeführt werden. In concat wird zuerst mit dem Standardkonstruktor ein neues, leeres String-Objekt erzeugt. In diesem wird dann ein Character-Array mit dem Inhalt "abcd" angelegt und das neue String-Objekt wird byvalue zurückgegeben. Das bedeutet konkret, dass auf dem Stack eine mit dem Kopierkonstruktor erzeugte temporäre Kopie des zurückgegebenen Objektes gespeichert wird und das lokale String- Objekt ("abcd") der Methode concat mit dem Destruktor zerstört wird. Der gleiche Ablauf wiederholt sich für den zweiten Aufruf von concat, wobei diesmal der Kopierkonstruktor von s aufgerufen wird. Wiederum wird das lokale String-Objekt ("abcdef") der Methode concat mit dem Destruktor zerstört. Zudem können alle temporären String-Objekte ("abcd", "ab", "cd", "ef") zerstört werden. Ganz zum Schluss, nachdem s auf der Konsole ausgegeben worden ist, wird auch noch s zerstört, weil das Programm zu Ende ist. Implementieren Sie nun den Verschiebekonstruktor für Ihre String-Klasse, so dass er auch eine kurze Meldung auf die Konsole ausgibt und führen Sie dann dasselbe Programm wie oben aus. Dokumentieren Sie in der gezeigten Form (Screenshot und Text), was nun genau passiert. 2 Zusammenfassung der Aufgaben Hier finden Sie nochmals eine Zusammenstellung aller Aufgaben. 1. String-Klasse vorerst ohne Move-Semantik entwickeln. 2. Eigene Unit-Tests entwickeln und String-Klasse damit testen. 3. Vorschlag ausarbeiten, wie der Einsatz des Zuweisungsoperators unterbunden werden kann. 4. Verschiebekonstruktor für die String-Klasse entwickeln und Unterschied dokumentieren. 5. Vollständige String-Klasse, Unit-Tests, Unit-Test-Resultate und Code-Coverage abgeben. 6. Von uns zur Verfügung gestellte Unit-Tests ausführen. 7. Falls die Tests nicht erfolgreich sind, passen Sie Ihre String-Klasse an. 8. Überarbeitete String-Klasse, Unit-Test-Resultate und Code-Coverage abgeben. 6