Erstellen eines Iterators durch eine std::list in constanter Methode nicht möglich


Status
Dieses Thema wurde gelöst! Zur Lösung gehen…
#1
Hallo,
ich sitze gerade an einer c++ Hausaufgabe und verstehe nicht, weshalb die Erstellung eines Iterators Probleme bereitet.

Header / Klassendefinition
C++:
#ifndef _WEATHERSTATIONS_
#define _WEATHERSTATIONS_

#include <list>
#include "WeatherStation.h"

class WeatherStations {
public:

    // Copy constructor
    WeatherStations(WeatherStations const &ws);

    // Assignment operator
    WeatherStations &operator =(WeatherStations const &ws);

    // Inserts by name a new weather station.
    // If a weather station already exists, it is replaced.
    void Add(WeatherStation const& ws);

    // Removes a weather station
    bool Remove(WeatherStation const& ws);

    // Returns the number of weather stations
    size_t GetNrStations() const;

    // Prints all weather stations
    void PrintAll(std::ostream& out) const;

    // Print coldest/warmest weather station
    void PrintColdest(std::ostream& out) const;
    void PrintWarmest(std::ostream& out) const;

private:
    // member variables
    size_t maxStations;
    size_t contain;
  
    std::list<WeatherStation> stations;
};

#endif
Printfunktion im cpp-File:
C++:
void WeatherStations::PrintAll(std::ostream & out) const
{
    for (std::list<WeatherStation>::iterator iter = stations.begin(); iter != stations.end(); iter++)
    {
        iter->Print(out);
    }
}
Visual Studio markiert mir folgenden Fehler:
1558260564091.png

Ich weiß, dass konstante Methoden die Membervariablen des Klassenobjektes nicht verändern dürfen, weshalb die Verwendung für Ausgabefunktionen ja auch Sinn macht. Aber hier wird ja auch nur ein temporärer Iterator in der Methode erzeugt. Scheinbar gibt es aber dabei schon das Problem. Nehme ich das const am Ende der Funktionsdeklaration weg, funktioniert es - ist aber natürlich nicht die Aufgabe.

Danke schon einmal für Antworten!

Grüße,
Neonof
 

Technipion

Erfahrenes Mitglied
#4
Ein normaler Iterator zeigt auf veränderliche Elemente, d.h. aber im Prinzip, dass du damit in deiner for-Schleife auch schreibenden Zugriff hättest (was ja wegen der constness der Funktion nicht erlaubt ist). Also meckert der Compiler, dass er einen std::list::iterator nicht in einen std::list::const_iterator umwandeln kann (was man/er erwarten würde). Denn ein const_iterator ist zwar selbst nicht const (damit er inkrementiert werden kann), zeigt aber auf const Elemente. Damit ist dann sichergestellt, dass du auch wirklich nur liest ;)

Du kannst dir mal merken: In C++ solltest du immer wenn es möglich ist const benutzen.
Damit ersparst du dir selbst unter Umständen viel Fehlersuche :p
 

cwriter

Erfahrenes Mitglied
#5
Du kannst dir mal merken: In C++ solltest du immer wenn es möglich ist const benutzen.
Yep, und ich würde "auto/decltype" zu der Liste hinzutun ;)
C++:
void WeatherStations::PrintAll(std::ostream & out) const
{
    for (std::list<WeatherStation>::iterator iter = stations.begin(); iter != stations.end(); iter++)
    {
        iter->Print(out);
    }
}
// Mit decltype:
void WeatherStations::PrintAll(std::ostream & out) const
{
    for (decltype(stations)::const_iterator iter = stations.begin(); iter != stations.end(); iter++)
    {
        iter->Print(out);
    }
}

// In diesem Fall sogar mit der ':'-Notation:
void WeatherStations::PrintAll(std::ostream & out) const
{
    for (const auto& elem : stations)
    {
        elem.Print(out);
    }
}
Damit wird der Code nicht nur leserlicher, es ist auch noch besser für Refactoring (so könntest du stations in einen std::vector verwandeln und der Code funktioniert noch). Davon abgesehen willst du ja nur alle Stations in einer Collection abfragen, der List-Typ ist dabei unerheblich.

Ansonsten hat @Technipion ja schon alles gesagt :)

Gruss
cwriter
 
#6
Das sind verdammt gute Inputs. Ich schreib den Code um und gewöhn es mir an.
Eure Tipps haben mir beim Lernen von c damals verdammt gut geholfen.

Grüße,
Neonof
 
Zuletzt bearbeitet:
#7
Als kleine Frage hinten dran, statt gleich ein neues Thema zu eröffnen:
1558265263657.png
Diesen Fehler zeigt er mir beim kompilieren.

WeatherStation.h
C++:
#ifndef _WEATHERSTATION_
#define _WEATHERSTATION_

#include <iostream>
#include <string>

class WeatherStation {
public:

    // Constructor
    WeatherStation(std::string const &name = "", double celsius = 0, double humidity = 0);

    // Accessor methods
    std::string const& GetName() const;
    void SetName(std::string const& name);
    double GetCelsius() const;
    void SetCelsius(double c);
    double GetFahrenheit() const;
    void SetFahrenheit(double f);
    double GetHumidity() const;
    void SetHumidity(double h);

    // Print the data of the weather station (Name, C, F, humidity)
    void Print(std::ostream &out) const;

private:
    std::string mName; // Name of the weather station
    double mCelsius; // Temperature in C
    double mHumidity; // Humidity in %
};

inline std::ostream& operator <<(std::ostream &out, WeatherStation const &ws) {
    ws.Print(out);
    return out;
}

bool operator ==(WeatherStation const &a, WeatherStation const &b) {
    return (a.GetName() == b.GetName()) && (a.GetCelsius() == b.GetCelsius()) && (a.GetHumidity() == b.GetHumidity());
}

#endif
main.cpp
C++:
#include <iostream>
#include "WeatherStation.h"
#include "WeatherStations.h"

int main()
{
    WeatherStations stations;
    stations.Add(WeatherStation("station 1", 30, 20));
    stations.PrintAll(std::cout);
    return 0;
}
Vielleicht wisst ihr das aus Erfahrung mit Visual Studio auch gleich? ^^'

Grüße,
Neonof
 
Zuletzt bearbeitet:

cwriter

Erfahrenes Mitglied
#8
Vielleicht wisst ihr das aus Erfahrung mit Visual Studio auch gleich? ^^'
Zur Abwechslung ist das kein Problem von VS, sondern C++: Die Headerdatei wird mehr als einmal gelesen, daher wird operator== mehrfach definiert. Ein "inline" davor wie bei operator>> oder direkt in die Klasse integrieren sollte das Problem lösen (inline sagt dem Compiler, dass dasselbe Symbol (mit identischer Definition) mehrmals vorkommen darf)

Gruss
cwriter
 

cwriter

Erfahrenes Mitglied
#10
Sollten die Präprozessoranweisungen in den Headerfiles nicht doppeltes Einlesen verhindern?
Eine sehr gute Frage.

Wir müssen das von einem anderen Winkel betrachten:
In C sind Headerfiles eigentlich nur Verweise für den Linker. Die Deklarationen sind dann etwa
C:
int add(int a, int b);
Das sagt dem Compiler nur: "Hey, wenn das Symbol "add" als Funktion gebraucht wird, ist das ok: Ich garantiere dir, dass der Linker dann weiss, was er dort einsetzen soll".
In C speziell: Es ist erlaubt, undeklarierte Funktionen zu verwenden (gibt eine Warnung - "implict declaration", wenn ich nicht irre).
Der Linker sucht dann die Funktion und setzt sie ein - Problem gelöst. In C ist daher das Prinzip der Object-Files sehr verbreitet: Aus jeder .c-Datei gibt es ein Object File, die Header sind (fast) nur aus Conveniencegründen vorhanden.

C++ ist viel komplexer. Nicht nur gibt es overloaded functions (daher sind die Funktionssignaturen "mangled"), sondern man hat auch festgestellt, dass viele Funktionen sehr klein sind. Im Beispiel von add() wäre die Definition ja nur ein:
C:
int add(int a, int b)
{
    return a + b;
}
(Anmerkung: Link Time Optimization gibt es mittlerweile auch, allerdings ist die recht teuer und nicht sehr komplett (-lto))
Daher ist "inlining" sehr viel wichtiger geworden - C++ Templates sind eine sehr mächtige Form von inline-Code.
Dieses Inlining bedeutet, dass der Compiler die Funktion anschaut, die benutzt wird, und den Code passend zum Caller-Context einsetzt. Damit kann er z.B. ifs, die in diesem Kontext nie true sein können, entfernen. Damit sieht er die Funktion aber mehrmals (Jede Benutzung 1x, da er das Symbol kennt und den Code lesen muss, um es entsprechend einzusetzen). Mit inline erlaubst du das.
Insbesondere ist C++ sehr viel Header-lastiger als C. Die Typen sind strikter, alles muss definiert sein etc. Der grosse Vorteil: Man kann fast allen Code in Header packen. Der Nachteil: Bei sich gegenseitig Referenzierenden/including Headern ist die Reihenfolge sehr wichtig - oft muss man Funktionen in .cpp-Dateien packen, um circular Dependencies etc. zu lösen. Und: Rekompilierung nach Änderungen geht länger, da alles, was den veränderten Header nutzt, nochmals kompiliert werden muss (.cpp-Dateien werden nicht included, daher muss dort nur ein Object File neu erstellt werden).

Achtung: inline bedeutet nicht, dass die Funktion tatsächlich inline generiert wird - der Compiler kann es auch in eine eigene Funktion auslagern (vgl. g++ und `__force_inline`).

Für echt-inline-Funktionen gibt es in modernem C++ constexpr functions.

TL;DR: Der Compiler schaut sich das Symbol im eigenen Funktionscache an. Daher helfen die Include Guards nicht.

Gruss
cwriter
 
#11
Ich hab die Funktion in die Klasse eingebunden und jetzt funktioniert alles (y)
Danke für die ausführlichen Erklärungen!

Grüße,
Erakla
 
Status
Dieses Thema wurde gelöst! Zur Lösung gehen…