Tracks
/
C++
C++
/
Übungen
/
Die Macht von Troy
Die Macht von Troy

Die Macht von Troy

Lernübung

Einführung

Das Schlüsselwort auto in C++

In C++ ist das Schlüsselwort auto ein mächtiges Feature, das mit C++11 eingeführt wurde. Du verwendest es, um Variablen mit einem abgeleiteten Datentyp zu deklarieren. Der Compiler leitet den Typ der Variablen aus ihrem Initialisierungswert ab. Das kann den Code lesbarer und einfacher zu warten machen.

Verwendungsbeispiel

Schauen wir uns das folgende Beispiel an, in dem auto zum Deklarieren von Variablen verwendet wird:

auto dragon_population{3};          // dragon_population is deduced as an integer
auto westeros{7.7777};              // westeros is deduced as a double
auto wedding_location{"The Twins"}; // wedding_location is deduced as a const char*, not std::string

In jedem Fall wird der Typ der Variable aus dem Wert abgeleitet, mit dem sie initialisiert wird.

Typableitung

Das Schlüsselwort auto hilft dir, prägnanteren und lesbareren Code zu schreiben, weil du explizite Typen nicht mehr so ausführlich hinschreiben musst.

const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");

Hier leitet auto den Typ von purple_wedding_pie als std::vector<std::string> ab, sodass du den Typ nicht erneut explizit angeben musst.

Kompatibilität

Das Schlüsselwort auto ist mit verschiedenen C++-Konstrukten kompatibel und damit ein vielseitiges Werkzeug in der modernen C++-Programmierung.

auto& element{array[0]};           // reference to an element
const auto object{otherObject};    // const type version of otherObject's type
auto* ptr{&x};                     // pointer to x with the same type as x, but as a pointer.

In späteren Konzepten wirst du das Schlüsselwort auto oft zusammen mit Lambda-Ausdrücken, bereichsbasierten for-Schleifen und Iteratoren sehen.

Smart Pointer

Smart Pointer sind ein modernes C++-Feature für die automatische Speicherverwaltung. Sie helfen, Speicherlecks und hängende Zeiger zu vermeiden, wie sie bei rohen Zeigern häufig vorkommen. Sie wirken als Hüllen um rohe Zeiger und fügen zusätzliche Funktionalität hinzu, etwa das automatische Freigeben des Speichers, wenn der Zeiger nicht mehr gebraucht wird.

Allgemeine Syntax

Smart Pointer sind in der C++-Standardbibliothek typischerweise als Klassentemplates implementiert. Die beiden am häufigsten verwendeten Smart Pointer sind std::unique_ptr und std::shared_ptr.

Unique Pointer

std::unique_ptr ist ein Smart Pointer, der das Objekt exklusiv besitzt. Er stellt sicher, dass zu jedem Zeitpunkt nur ein std::unique_ptr-Objekt die Ressource besitzt. Wenn der besitzende std::unique_ptr zerstört oder zurückgesetzt wird, zerstört er automatisch das Objekt und gibt seinen Speicher frei.

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

Vorteile von std::make_unique()

Wenn du einen std::unique_ptr erstellst, solltest du std::make_unique() verwenden, statt mit new direkt Speicher zu reservieren. std::make_unique() bietet mehrere Vorteile:

  1. Ausnahmesicherheit: std::make_unique() garantiert Ausnahmesicherheit. Wird während der Konstruktion des Objekts eine Ausnahme ausgelöst, wird der Speicher automatisch freigegeben, sodass keine Speicherlecks entstehen.
  2. Klarheit: std::make_unique() macht den Code klarer und prägnanter. Du musst den Typ, der reserviert wird, nicht mehr explizit angeben, weil die Template-Argumente automatisch abgeleitet werden.
  3. Optimierungsmöglichkeiten: Compiler können std::make_unique() besser optimieren als eine manuelle Speicherreservierung mit new, was die Leistung verbessern kann.
  4. Vermeidung von Fehlgebrauch: Wenn du den std::unique_ptr manuell konstruierst, kannst du die zugrunde liegende Ressource löschen. Das führt zu undefiniertem Verhalten, wenn der std::unique_ptr sie am Ende seines Gültigkeitsbereichs zu löschen versucht.

Shared Pointer

std::shared_ptr ist ein Smart Pointer, mit dem mehrere std::shared_ptr-Objekte gemeinsam den Besitz an derselben Ressource teilen können. Er zählt mit, wie viele Shared Pointer auf die Ressource verweisen, und gibt den Speicher erst frei, wenn der letzte Shared Pointer, der die Ressource besitzt, seinen Gültigkeitsbereich verlässt oder zurückgesetzt wird.

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

In C++17 und darunter wird std::shared_ptr mit Arrays über std::make_shared<T[]> nicht direkt unterstützt. Arrays lassen sich zwar mit std::make_shared<T[]> reservieren, aber Shared Pointer direkt daraus zu erstellen kann zu undefiniertem Verhalten führen, weil sich die Speicherverwaltung von einzelnen Objekten und Arrays unterscheidet. Verwende stattdessen std::vector oder eigene Löschfunktionen, um Arrays mit Shared Pointern effektiv zu verwalten. Achte bei Array-Reservierungen und Shared Pointern in C++17 immer auf die Kompatibilität mit deinem Compiler und deiner Standardbibliotheks-Implementierung.

Vorteile von std::make_shared()

Ähnlich wie std::make_unique() bietet std::make_shared() Vorteile wie eine bessere Speichereffizienz, Ausnahmesicherheit und Lesbarkeit. Es fasst die Speicherreservierung für den Kontrollblock und das verwaltete Objekt in einem einzigen Schritt zusammen. Das erhöht die Effizienz und verringert das Risiko von Speicherlecks. Außerdem vereinfacht die automatische Ableitung der Template-Argumente den Code und verbessert die Lesbarkeit. Mit std::make_shared() schreibst du saubereren, sichereren und effizienteren Code, wenn du mit std::shared_ptr-Objekten in C++ arbeitest.

Empfehlungen zur Verwendung

Verwende standardmäßig Smart Pointer: std::unique_ptr für exklusiven Besitz und std::shared_ptr für gemeinsamen Besitz. Rohe Zeiger reservierst du für nicht-besitzende Referenzen oder für die Anbindung an alten Code. In den meisten Fällen reicht std::unique_ptr für exklusiven Besitz aus, denn er bietet eine schlanke Speicherverwaltung ohne den Overhead der Referenzzählung. std::shared_ptr solltest du sparsam einsetzen, denn er bringt Overhead und Komplexität mit sich, solange du nicht wirklich gemeinsamen Besitz brauchst.

Anleitung

Diese Übung entführt dich in die Welt von Troja. Das Leben seiner Bewohner ist voller Wunder und Magie. Viele Menschen in Troja besitzen Kräfte, die sie in ihrem Alltag häufig einsetzen. Mit Kräften wird die Welt umgeformt oder die Fauna von Troja und andere Menschen beeinflusst. Magie zeigt sich außerdem in einzigartigen Artefakten, die bei Abenteurern, Handwerkern und Weisen sehr begehrt sind.

In dieser Übung schreibst du Code, um die Menschen von Troja, ihre besessenen Artefakte und die Wechselwirkungen ihrer Kräfte zu modellieren.

Du hast sechs Aufgaben. Die erste befasst sich mit der Erschaffung eines Menschen, die anderen fünf mit dem Umgang mit Kräften und Artefakten.

1. Bringe Menschen in die Welt von Troja

Für dein Modell von Troja sind Menschen das wichtigste Element. Dein Modellmensch sollte ein einzigartiges Artefakt besitzen können. Er sollte außerdem eine Kraft manifestieren können. Diese Kräfte können andere Menschen beeinflussen, deshalb möchtest du auch modellieren, ob ein Mensch von einer anderen Kraft beeinflusst wird.

Dir stehen grundlegende Implementierungen der artifact- und power-Structs zur Verfügung. Implementiere einen human-Struct (oder eine Klasse), der einen Smart Pointer auf eine artifact-Membervariable namens possession besitzt. Jedes Artefakt kann zu jedem Zeitpunkt nur von einem einzigen Menschen besessen werden.

Ein human muss zwei weitere Membervariablen haben. Eine hält seine own_power, die andere ist eine Kraft, von der er beeinflusst wird (influenced_by). Sowohl own_power als auch influenced_by sind Smart Pointer auf powers. Jede power kann von einem einzigen Menschen besessen werden, aber gleichzeitig auch andere Menschen beeinflussen.

Standardmäßig werden Menschen ohne Artefakt geboren, besitzen keine Kräfte und werden von keinen beeinflusst.

human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr

2. Bringe Artefakte nach Troja

Dein Modell ist langweilig ohne das Zusammenspiel seiner Teile. Du möchtest einzigartige Artefakte erschaffen und sie bestimmten Menschen geben.

Definiere die Funktion give_new_artifact, die nichts zurückgibt, aber eine Referenz auf einen human und einen string entgegennimmt. Mit dem string soll sie ein neues artifact-Objekt anlegen und den possession-Zeiger des human entsprechend setzen. Die Funktion sollte nichts zurückgeben.

human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");

erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"

3. Mach Gegenstände handelbar

In der Welt von Troja dreht sich alles um Interaktion. Du möchtest, dass Menschen handeln, indem sie ihre Besitztümer austauschen.

Schreibe eine Funktion exchange_artifacts, die nichts zurückgibt, aber zwei Artefakt-Smart-Pointer entgegennimmt, um die Gegenstände zu tauschen. Denk daran, dass du einen unique_ptr nicht kopieren kannst. Das gilt auch für die Verwendung in Funktionsparametern. Verwende stattdessen eine Referenz auf den unique_ptr.

human uchiha{};
give_new_artifact(uchiha, "konoha headband");
human uzumaki{};
give_new_artifact(uzumaki, "forehead protector");

exchange_artifacts(uchiha.possession, uzumaki.possession);

uchiha.possession->name;
// "forehead protector"
uzumaki.possession->name;
// "konoha headband"

4. Gib den Menschen Kraft

Das aufregendste Merkmal von Troja sind die besonderen Kräfte, die Menschen besitzen können. Einige können Eisen mit ihren Gedanken schmelzen, während andere nachts jede Wunde sofort heilen können.

Definiere die Funktion manifest_power, die nichts zurückgibt, aber eine Referenz auf einen human und einen string entgegennimmt. Mit dem string soll sie ein neues power-Objekt anlegen und den own_power-Zeiger des human entsprechend setzen. Die Funktion sollte nichts zurückgeben.

human eleven {};
manifest_power(eleven, "psychokinesis");

eleven.own_power->effect;
// "psychokinesis"

5. Nutze die Kraft

Was nützen die größten Kräfte, wenn man sie nicht einsetzen kann? Dein Modell konzentriert sich auf Menschen, also möchtest du den Einfluss von Kräften verfolgen.

Schreibe eine void-Funktion use_power, die zwei Referenzen auf Menschen entgegennimmt. Der erste Mensch ist der Wirkende und der zweite stellt das Ziel dar. Der influenced_by-Zeiger des Ziels sollte auf die Kraft des Wirkenden zeigen.

Der Einfachheit halber können Menschen nur von einer einzigen Kraft beeinflusst werden. Diese Kraft bleibt bestehen, auch wenn der Wirkende nicht mehr existiert.

human pamela_isley{};
manifest_power(pamela_isley, "control pheromones");

human count_vertigo{};
use_power(pamela_isley, count_vertigo);
count_vertigo.influenced_by->effect;
// "control pheromones"

6. Behalte die Intensität der Kraft im Auge

Bestimmte Kräfte verlieren ihre Wirkung oder lösen in deiner Simulation bestimmte Effekte aus, wenn sie auf mehrere Menschen angewendet werden. Du möchtest die Anzahl der Menschen verfolgen, die mit jeder Kraft verbunden sind.

Definiere die Funktion power_intensity, die einen Menschen entgegennimmt und die Intensität seiner Kraft als int zurückgibt. Wenn die Person keine Kraft hat, sollte der Rückgabewert 0 sein. Andernfalls sollte die Intensität den Wirkenden und alle aktuell beeinflussten Menschen widerspiegeln.

human jean_grey{};
manifest_power(jean_grey, "uplifting personality");

human scott{};
human logan{};
human ororo{};

use_power(jean_grey, ororo);
use_power(jean_grey, logan);
use_power(jean_grey, scott);

power_intensity(jean_grey);
// 4
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
C++ Exercism

Bereit, mit Die Macht von Troy zu starten?

Melde dich bei Exercism an, um C++ mit 19 Konzepte100 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.