#include "pxtn.h"
#include "fixarray.h"
PN_(Basic Tests for Class `fixarray`)
int main() {
my::fixarray<double> a{3};
a[0] = 2.5; PX_("2.5", a[0]);
a[1] = a[0]/2; PX_("1.25", a[1]);
a[2] = a[0] + a[1]; PX_("3.75", a[2]);
PX_("2.5", *a.data());
}
Die etwas kürzere Übung für nach der Mittagspause
=================================================
Schreiben Sie eine Template-Klasse `my::fixarray` welche ein Array fester Größe
auf dem Heap anlegt.
GENAU WIE beim `std::array` ist die Klasse der Elemente eingeschränkt
hinsichtlich dessen, dass diese einen Default-Konstrukor besitzen
muss und direkt beim Anlegen alle Elemente mit diesem initialisiert
werden.
ANDERS ALS beim `std::array` muss die Anzahl der Elemente aber NICHT schon zur
Compilezeit sondern erst zur Laufzeit festgelegt werden.
ANDERS ALS beim `std::vector` soll die Implementierung NUR EINEN Zeiger sowie
sowie die Anzahl der Elemente speichern.
Bitte bearbeiten Sie vorerst nur den ersten und zweiten Teil. Den Rest können
Sie optional zu einem späteren Zeitpunkt angehen … oder auch nur die letzte
Ausbaustufe ansehen. Diese Schritte haben weniger "Modernes C++" zum Inhalt,
vielmehr illustrieren sie den grundsätzlichen Ansatz für die Implementierung
von Container-Klassen an einem realistischen Beispiel.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
1. Teil:
Beginnen Sie mit dem in `main.cpp` bzw. `fixarray.h` enthaltenen Minimalumfang.
Analysieren Sie den vorgegebenen Code und fügen Sie weitere Testfälle hinzu,
sodass ALLE Konstruktoren und Member-Funktionen zumindest EINMAL aufgerufen
werden.
Frage: Warum ist sind die Member-Funktionen `operator[]` und `data` zweimal mit
(scheinbar) ein und derselben Implementierung vorhanden?
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2. Teil:
Ersetzen Sie den bisherigen Zeiger (= native C-style pointer) durch einen
`std::unique_ptr` und passen Sie die Member-Funktionen entsprechend an.
Frage: Welche Member-Funktionen können Sie nun einfach entfernen?
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
3. Teil:
Fügen Sie nun weitere Member-Funktionen hinzu, die bei STL-Containern üblich
sind, z.B.
* `at()` für den Zugriff mit Indexprüfung;
* Kopierkonstruktor und -zuweisung zwischen zwei Objekten der Klasse (dabei
aber auch die möglicherweise unterschiedliche Größe beachten!);
Und natürlich auch: fügen Sie Tests für alle neu implementierten Features
hinzu.
(Oder praktizieren Sie striktes TDD: Sie fügen ZUERST den Test hinzu, also was
die Klasse unterstützen SOLL, und schreiben erst DANACH die Implementierung.)
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
4. Teil:
Fügen Sie einen Konstruktor für `std::initializer_list` und sowie für ein
generisches Iteratorpaar hinzu. Letzterer soll als ERSTES Argument die Größe
erhalten, danach die beiden (Input-) Interatoren.
Zusatzfrage: Auf welche "Überaschung" müssen Sie gefasst sein, wenn Sie alle
bisherigen Tests im Testcode belassen?
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
5. Teil:
Falls nicht schon im letzten Teil geschehen, überlegen Sie nun ob die beiden
dort hinzugefügten Konstruktoren evtl. aufeinander aufbauen könnten (also
einer der beiden verwendet intern den anderen) und diskutieren Sie ob an
dieser Stelle Konstruktor-Vererbung zum Einsatz kommen sollte.
Versehen Sie außerdem die Klasse `fixarray` selbst mit der für Container
üblichen Iteratorschnittstelle, so dass sie sowohl mit STL-Algorithmen als auch
bereichsbasierten `for`-Schleifen verwendbar wird.
#include <memory>
namespace my {
template<typename T>
class fixarray {
// replace next line with this:
// std::unique_ptr<T[]> arr; // <------------------
// and all related changes, e.g.
// no more destructor
T *const arr;
public:
fixarray(std::size_t s)
: arr{new T[s]}
{}
fixarray(const fixarray&) =delete;
fixarray(fixarray&& init)
: arr{init.arr}
{
init.arr = nullptr;
}
fixarray()
: arr{nullptr}
{}
~fixarray() {
delete[] arr;
}
fixarray& operator=(const fixarray&) =delete;
fixarray& operator=(fixarray&& rhs) {
if (this != &rhs) {
delete[] arr;
arr = rhs.arr;
}
return *this;
}
T& operator[](std::size_t idx) {
return arr[idx];
}
const T& operator()(std::size_t idx) const {
return arr[idx];
}
T* data() { return arr; }
const T* data() const { return arr; };
};
}
#ifndef PXT_H
#define PXT_H
#include <iostream>
#include <sstream>
#define PX(...)\
((void)(std::cout << "::: " << __FUNCTION__\
<< '[' << __LINE__ << ']'\
<< "\t" #__VA_ARGS__ " --> " << (__VA_ARGS__)\
<< std::endl))
#define PX_(expect, ...)\
do {\
std::ostringstream result;\
result.flags(std::cout.flags());\
result.precision(std::cout.precision());\
result << (__VA_ARGS__);\
++PN_::done;\
if (result.str() == expect) break;\
std::cout << "::: " << __FUNCTION__ << '[' << __LINE__ << ']'\
<< "\t" #__VA_ARGS__ " --> " << result.str()\
<< "\t!= " << expect << std::endl;\
++PN_::fail;\
} while (false)
#define PN_(testname)\
struct PN_ {\
PN_() {\
std::cout << "... " #testname << std::endl;\
}\
~PN_() {\
std::cout << "=== " << __FILE__ << ": "\
<< done << " tests ";\
if (fail) std::cout << ", " << fail\
<< " FAILED";\
else std::cout << "passed (OK)";\
std::cout << std::endl;\
}\
static int done, fail;\
} PN_test;\
int PN_::done, PN_::fail;
#endif