#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <sstream>
using namespace std;
#include "Airport.h"
#include "Connection.h"
static const char *const help = R"(
Use one of the following (which means)
SETUP -------------------------------------------------------
+ airport ... (add given airport(s))
- airport ... (remove given airport(s))
: flight [airport airport ...] (define connection)
QUERY -------------------------------------------------------
< [select] (list outgoing flights from matching airport(s))
> [select] (list incomming flights to matching airport(s))
= [select] (list route of matching flight(s))
OTHER -------------------------------------------------------
$ filename (read commands from filename)
? (show this help page)
! (check memory usage)
. (terminate)
)";
class ParseException :
virtual public runtime_error {
public:
ParseException(const string& reason)
: runtime_error("ERROR " + reason)
{}
};
bool parse(const string& line, const std::string& incfile = std::string()) {
static map<string, shared_ptr<Connection>>knownConnections;
static map<string, shared_ptr<Airport>> knownAirports;
istringstream is(line + " ");
char cmdc;
if (!(is >> cmdc).good())
return false;
if (!incfile.empty())
cout << '$' << incfile << ' ' << line << std::endl;
string airportName;
string flightNumber;
string includeFile;
switch (cmdc) {
case '+':
while (is >> airportName) {
if (knownAirports.count(airportName)) {
cout << airportName << " already in database" << endl;
return true;
}
knownAirports.insert(make_pair(airportName, make_shared<Airport>(airportName)));
cout << "added airport " << airportName
<< " to database" << endl;
}
return true;
case '-':
while (is >> airportName) {
knownAirports.erase(airportName);
cout << "removed airport " << airportName
<< " from database" << endl;
}
return true;
case ':':
if (is >> flightNumber) {
const auto existingConnection = knownConnections.find(flightNumber);
const bool isKnownConnection = (existingConnection != knownConnections.end());
if (isKnownConnection) {
for (const auto e : knownAirports) {
e.second->removeConnection(existingConnection->second);
}
knownConnections.erase(flightNumber);
}
vector<shared_ptr<Airport>> visitedAirports;
while (is >> airportName) {
auto foundAirport = knownAirports.find(airportName);
if (foundAirport == knownAirports.end()) {
throw ParseException("no such airport: " + airportName);
}
if (find(visitedAirports.begin(), visitedAirports.end(), foundAirport->second)
!= visitedAirports.end()) {
throw ParseException("duplicate airport:" + airportName);
return true;
}
visitedAirports.push_back(foundAirport->second);
}
if (visitedAirports.size() < 2) {
knownConnections.erase(flightNumber);
if (!visitedAirports.empty())
throw ParseException("too few airports:" + visitedAirports.front()->getName());
if (isKnownConnection) {
cout << "removed connection " << flightNumber
<< " from database" << endl;
}
}
else {
const auto currentConnection = make_shared<Connection>(flightNumber);
knownConnections.insert(make_pair(flightNumber, currentConnection));
cout << (isKnownConnection ? "modifying" : "creating")
<< " connection " << flightNumber
<< " in database" << endl;
for (const auto currentAirport : visitedAirports) {
const auto currentConnection = knownConnections[flightNumber];
const auto nthStop = currentConnection->addAirport(currentAirport);
currentAirport->addConnection(currentConnection, nthStop);
cout << "added airport " << currentAirport->getName()
<< " to connection " << currentConnection->getFlight() << endl;
}
}
}
return true;
case '<':
case '>':
if (!(is >> airportName >> ws).eof())
airportName.clear();
for (const auto e : knownAirports) {
if (airportName != "*" && e.first.find(airportName) == string::npos)
continue;
cout << e.first << endl;
for (const auto c : e.second->getConnections()) {
const auto &li = (cmdc == '>')
? get<1>(c)->getComingFrom(get<0>(c))
: get<1>(c)->getGoingTo(get<0>(c))
;
if (!li.empty()) {
cout << ' ' << cmdc << ' ' << get<1>(c)->getFlight() << ':';
for (const auto a : li) {
cout << ' ' << a->getName();
}
cout << endl;
}
}
}
return true;
case '=':
if (!(is >> flightNumber >> ws).eof())
flightNumber.clear();
for (const auto e : knownConnections) {
if (flightNumber != "*" && e.first.find(flightNumber) == string::npos)
continue;
cout << e.first << " =";
for (const auto a : e.second->getAirports())
cout << ' ' << a->getName();
cout << endl;
}
return true;
case '!':
cout << Airport::instances << " airport(s), "
<< Connection::instances << " flight(s)"
<< endl;
return true;
case '$':
if (!(is >> includeFile))
throw ParseException("file name missing");
else {
ifstream ifs(includeFile);
if (!ifs.good())
throw ParseException("cannot open file: " + includeFile);
string line;
while (getline(ifs, line))
parse(line, includeFile + "$" + incfile);
}
return true;
case '#':
return true;
default:
cout << "unknown command: " << line << endl;
// FALLTHROUGH
case '?':
if (!incfile.empty())
return false;
cout << help << endl;
return true;
case '.':
if (!incfile.empty())
return true;
cout << "bye, bye" << endl;
return false;
}
}
int main() {
string line;
while (getline(cin, line)) {
try {
if (!parse(line))
break;
}
catch (const ParseException &ex) {
cout << ex.what() << endl;
}
}
}
Die etwas längere Übung für den Abschluss des Tages
===================================================
Bei dieser Aufgabe müssen Sie sich zunächst etwas Zeit nehmen um die Quelltexte
des `airtravel`-Projekts näher anzusehen.
Dieses besteht aus den wesentlichen zwei Klassen `Airport` und `Connection`,
welche jeweils Flughäfen und Flugverbindungen definieren.
Da die Flughäfen (über `std::shared_ptr`) auf die Flugverbindungen verweisen,
während diese wiederum (über `std::shared_ptr` auf die Flughäfen (zurück)
verweisen, besteht hier die prinzipielle Gefahr zyklischer Referenzen, welche
trotz der Verwendung referenzzählender Zeigern letzten Endes einen Memory-Leak
verursachen könnten.
Wenn Sie das Programm `./airtravel` gestarten haben, erhalten Sie mit `?` eine
Übersicht über die verfügbaren Kommandos, mit denen Flughäfen und -verbindungen
angelegt, aufgelistet und wieder gelöscht werden können.
Spielen Sie alle diese Möglichkeiten durch, um damit etwas vertraut zu werden.
Damit Sie nicht immer den Datenbestand händisch wieder neu aufbauen müssen, ist
die Datei `setup` vorbeitet, die Sie mit dem `$`-Kommando einlesen können.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
1. Teil:
Demonstrieren Sie zwei Szenarien, die jeweils damit beginnen, dass Sie das in
der Datei `setup` vorbereitete Szenario einlesen und anschließend einige Teile
davon wieder per Hand entfernen.
Je nachdem wie Sie dann weiter vorgehen, werden am Ende weder Flughafen-Objekte
noch Flugverbindungs-Objekte übrig bleiben ODER aber es gibt noch einige
solcher Objekte, die sich aber nicht mehr mit den Kommandos '>', '<' und '='
auflisten lassen.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2. Teil:
Bereiten Sie die Beseitigung des Memory-Leaks vor, indem Sie zunächst für die
expliziten `std::shared_ptr<Airport>` und `std::shared_ptr<Connection>` jeweils
einen Typ-Alias `AirportRef` und `ConnectionRef` einführen.
Ziel: Das Programm muss nach dieser Änderung fehlerfrei kompilierbar sein und es
wird natürlich weiterhin das Problem mit dem Memory-Leak haben.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
3. Teil:
Ändern Sie nun mindestens EINEN der beiden Typ-Aliase von `std::shared_ptr` in
`std::weak_ptr`, AUSGENOMMEN in `airtravel.cpp`, dort MÜSSEN `knownAirports`
und `knownConnections` weiterhin via `std::shared_ptr` auf `Airport`- bzw.
`Connection`-Objekte referenzieren, damit diese (mindestens) so lange am Leben
gehalten werden, wie sie dort bekannt sind.
NATÜRLICH WIRD ES JETZT FEHLER GEBEN.
Versuchen Sie diese zunächst mit dem minimal möglichen Aufwand zu beseitigen,
auch wenn das bedeutet, dass bei gelöschten Flughäfen oder Verbindungen in den
Übersichten nicht mehr alles in der früheren Form ausgegeben wird.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
4. Teil:
Da die im letzten Schritt ersetzten `std::shared_ptr` natürlich nicht grundlos
existierten, sollten Sie nun auch auch eine Lösung für den Fall finden, dass
aus dem `std::weak_ptr` u.U. kein `std::shared_ptr` mehr gemacht werden kann,
weil das ursprünglich referenzierte Objekt mittlerweile gelöscht wurde!
Die Weiterführung der Aufgabe hängt nun davon ab, welchen der `std::shared_ptr`
Sie zu einem `std::weak_ptr` gemacht haben:
* Wenn aus der Sicht der Flugverbindungen nun die Flughäfen nur noch über
`std::weak_ptr` erreichbar sind, sollte in der Auflistung der angeflogenen
Stationen auch bei einem zwischenzeitlich gelöschten Flughafen weiterhin
dessen Name erscheinen, aber mit dem nachgestellten Zusatz `[closed]`.
* Wenn dagegen aus der Sicht der Flughäfen nun die Flugverbindungen nur noch
über `std::weak_ptr` erreichbar sind, sollte bei einer weggefallenen
Flungverbindung diese stillschweigend aus der Übersicht der ankommenden oder
abgehenden Verbungen dieses Flughafens entfernt werden – UND natürlich auch
aus der Menge der `std::weak_ptr`, das dort gehalten wird.
#ifndef AIRPORT_H_
#define AIRPORT_H_
#include <list>
using std::list;
#include <memory>
using std::shared_ptr;
#include <string>
using std::string;
#include <tuple>
using std::tuple;
class Connection;
class Airport {
const string name;
list<tuple<size_t, shared_ptr<Connection>>> connections;
public:
Airport(const string &name_)
: name(name_)
{
++instances;
}
~Airport() {
--instances;
}
string getName() const {
return name;
}
void addConnection(shared_ptr<Connection> c, size_t n);
void removeConnection(shared_ptr<Connection> c);
const list<tuple<size_t, shared_ptr<Connection>>> &getConnections() const;
static size_t instances;
};
#endif /* AIRPORT_H_ */
#include "Airport.h"
#include "Connection.h"
using namespace std;
void Airport::addConnection(shared_ptr<Connection> c, size_t n) {
removeConnection(c);
connections.emplace_back(n, c);
}
void Airport::removeConnection(shared_ptr<Connection> c) {
connections.remove_if(
[c](const tuple<size_t, shared_ptr<Connection>> &_1) {
return get<1>(_1) == c;
}
);
}
const list<tuple<size_t, shared_ptr<Connection>>> &Airport::getConnections() const {
return connections;
}
size_t Airport::instances;
#ifndef CONNECTION_H_
#define CONNECTION_H_
#include <memory>
using std::shared_ptr;
#include <string>
using std::string;
#include <vector>
using std::vector;
class Airport;
class Connection {
const string flight;
vector<shared_ptr<Airport>> airports;
public:
Connection(const string &flight_)
: flight(flight_)
{
++instances;
}
~Connection() {
--instances;
}
string getFlight() const {
return flight;
}
const vector<shared_ptr<Airport>> &getAirports() const {
return airports;
}
size_t addAirport(shared_ptr<Airport> ap);
vector<shared_ptr<Airport>> getComingFrom(size_t to) const;
vector<shared_ptr<Airport>> getGoingTo(size_t from) const;
static size_t instances;
};
#endif /* CONNECTION_H_ */
#include "Airport.h"
#include "Connection.h"
using namespace std;
size_t Connection::addAirport(shared_ptr<Airport> ap) {
const auto result = airports.size();
airports.push_back(ap);
return result;
}
vector<shared_ptr<Airport>> Connection::getGoingTo(size_t from) const {
vector<shared_ptr<Airport>> result;
while (++from < airports.size())
result.push_back(airports.at(from));
return result;
}
vector<shared_ptr<Airport>> Connection::getComingFrom(size_t to) const {
vector<shared_ptr<Airport>> result;
for (size_t i = 0; i < to; ++i)
result.push_back(airports.at(i));
return result;
}
size_t Connection::instances;