templatisierte std::list



  • Hallo zusammen,

    Ich benutze MinGW unter Window XP, aktuelle Version. Nun habe ich folgendes Problem:

    Ich habe eine eine Liste von std::list abgeleitet

    class qListI : public std::list<T>
    

    Wenn ich diese Liste nun löschen möchte, geht folgender Code nicht:
    Die Fehlermeldung lautet: error: there are no arguments to 'begin' that depend on a template parameter, so a declaration of 'begin' must be available

    template <class T>
    void qListI<T>::Clear()
    {
    	erase(begin(), end());
    }
    

    Folgender Code funktioniert, so weit so gut:

    template <class T>
    void qListI<T>::Clear()
    {
    	this->erase(this->begin(), this->end());
    }
    

    Frage 1) Kann mir jemand das verständlich erläutern? Ich bin C++ Anfänger... 😕

    Jetzt benötige ich eine Methode, um ein Element zu löschen:

    template <class T>
    void qListI<T>::RemoveElement(const T  &_t)
    {
    	qListI<T>::iterator		this->it;      // <-------- wird nicht übersetzt
    	for (this->it=this->begin(); this->it!=this->end(); this->it++)
    	{
    		if (*(this->it) == _t)
    		{
    			this->erase(this->it, this->it);
    		}
    	}
    }
    

    Die markierte Zeile wird nicht übersetzt, Fehlermeldung:
    error: need 'typename' before 'qBase::qListI<T>::iterator' because 'qBase::qListI<T>' is a dependent scope
    ..\qQt\/../qBase/Src/qListI.inl:119:24: error: expected ';' before 'this'

    Frage 2) Wie kann ich die Methode formulieren, damit das geht? Wie gesagt... C++ Anfänger... 😕

    Vielen Dank für eure Hilfe



  • Graythorn schrieb:

    Nun habe ich folgendes Problem:

    Ich habe eine eine Liste von std::list abgeleitet

    Dann tu's nicht. 🤡



  • 314159265358979 schrieb:

    Graythorn schrieb:

    Nun habe ich folgendes Problem:

    Ich habe eine eine Liste von std::list abgeleitet

    Dann tu's nicht. 🤡

    Tätääää, wolle mer se rinlosse???? Tätaaaa



  • Graythorn schrieb:

    314159265358979 schrieb:

    Graythorn schrieb:

    Nun habe ich folgendes Problem:

    Ich habe eine eine Liste von std::list abgeleitet

    Dann tu's nicht. 🤡

    Tätääää, wolle mer se rinlosse???? Tätaaaa

    Bevor das jetz in Sinnlosigkeiten ausartet: Auch wenn PIs Kommentar wie so häufig von magelhaftem Umfang und absolut ungenügend (weil nicht vorhanden) begründet war, hat er in der Sache recht:

    Leite nicht von std::list ab. Dazu ist sie nicht geeignet. Allgemeiner sind die wenigsten Klassen der Standardbibliothek designed, um davon abzuleiten.

    Was die erste Fehlermeldung (bei Clear) sagt ist, dass der Compiler an der Stelle nicht weiß, was du mit begin() und end() und erase() meinst. Der Grund ist, dass er zu dem Zeitpunkt nicht sicher sein kann (und deswegen nicht hinschaut), ob std::list<T> denn auch ein begin() hat. Weil er garnicht erst hinschaut weiß er nicht, was qList erbt und was nicht.

    aber davon mal abgesehn, dein Clear ist überflüssig, std::list hat bereits eine Methode clear.

    Zu deiner zweiten Methode:

    1. Zeile 4 wird aus vielen Gründen nicht übersetzt. Erstens weiß der Compiler nicht, was qListI<T>::iterator ist. iterator könnte eine Funktion in qListI sein, oder eine (statische) Variable, oder ein Typ. Wenn du nichts genaueres dazu schreibst, geht der Compiler von einer Variablen aus. Einen Typ musst du daher explizit mit typename anzeigen. Das sollte auch im Lehrbuch deiner Wahl stehen. Auch damit wirds nicht nicht übersetzt, weil du erst einen Typen angibst und dann einen Ausdruck. Was soll das sein? Eine Variablendeklaration? Dann lass das this-> weg.
    2. In deiner Schleife ist erase(it, it) unnötig kompliziert und fehlerhaft: erase mit zwei Argumenten löscht alles zwischen dem ersten und dem zweiten iterator. Zweimal der gleiche Iterator löscht also garnichts. Außerdem gibt es eine Überladung von erase, der man nur einen Iterator mitgibt, damit erreichst du genau was du möchtest: du löscht ein einzelnes Element.
      Dann hast du ein anderes Problem: Wenn du in deiner Schleife das erste Element gefunden und gelöscht hast, wird der eben gelöscht Iterator ungültig, du iterierst damit aber munter weiter, das erzegt undefiniertes Verhalten.
      Zu guter Letzt sollte man das nicht mit handgestrickten Schleifen lösen, sondern mit den Algorithmen der Standardbibliothek:
      std::remove macht genau, was du möchtest, performant, sicher und ohne Fehler.


  • Die STL-Container sind NICHT zum Ableiten gedacht und machen, sobald Polymorphie ins Spiel kommt, gewaltige Probleme. Lass es sein und verpass deiner Klasse eine std::list als Member.



  • Was die erste Fehlermeldung (bei Clear) sagt ist, dass der Compiler an der Stelle nicht weiß, was du mit begin() und end() und erase() meinst. Der Grund ist, dass er zu dem Zeitpunkt nicht sicher sein kann (und deswegen nicht hinschaut), ob std::list<T> denn auch ein begin() hat. Weil er garnicht erst hinschaut weiß er nicht, was qList erbt und was nicht.

    Nur zum Verständnis:
    Warum weiß er das nicht? Die Klasse ist doch von stl::list abgeleitet, das steht in der Deklaration. Diese Klasse ist jedoch bekannt, also müßte er doch wissen - und nachschauen können - das diese Methoden existieren...

    oder stehe ich auf dem Schlauch 😕



  • Du bist da über eine Besonderheit der Lookup-Regeln für Templates gestolpert. Ich versuch das mal auf ein einfaches Konstrukt zusammenzustreichen.

    template<typename T> struct A { 
      void foo() const {}
    };
    
    template<typename T> struct B : A<T> {
      void bar() const { foo(); }
    };
    
    int main() {
      B<int> b;
      b.bar();
    }
    

    funktioniert nicht. Warum nicht? Weil A<T>::foo() in B nicht gefunden (nicht gesucht!) wird. Der Grund dafür ist, dass in B noch nicht sicher bekannt ist, ob A<T> letztendlich eine Methode foo haben wird. Es wäre beispielsweise möglich,

    template<> struct A<int> { }; // kein foo()!
    

    dahinter zu setzen. B sucht zu diesem Zeitpunkt zunächst nur in den umgebenden Namensräumen, d.h.

    template<typename T> struct A { 
      void foo() const {}
    };
    
    void foo() { } // <-- das hier wird benutzt!
    
    template<typename T> struct B : A<T> {
      void bar() const { foo(); }
    };
    
    int main() {
      B<int> b;
      b.bar();
    }
    

    funktioniert wieder, wenn auch auf unerwartete Weise.

    Um B ohne die explizite dauernde Verwendung von dependent names dazu zu bringen, in this nachzusehen, muss also explizit deklariert werden, wo die Symbole herkommen sollen. In diesem Fall ginge das per

    template<typename T> struct A {
      void foo() const {}
    };
    
    template<typename T> struct B : A<T> {
      using A<T>::foo; // <-- hier
    
      void bar() const { foo(); }
    };
    
    int main() {
      B<int> b;
      b.bar();
    }
    

    Für Typnamen muss außerdem vorher bekannt gemacht werden, dass es sich um Typnamen handelt und wo sie herkommen sollen. Beispiel:

    template<typename T> struct A {
      typedef T foo_t;
      void foo() const {}
    };
    
    template<typename T> struct B : A<T> {
      typedef typename A<T>::foo_t foo_t;
      using A<T>::foo;
    
      void bar() const { foo(); }
    };
    

    Ungeachtet dieser Mechanik ist es allerdings tatsächlich ziemlich schlechter Stil, von Standardcontainern zu erben; diese sind nicht für Polymorphie ausgelegt. Du darfst jedenfalls nicht auf die Idee kommen, irgendwo

    std::shared_ptr<std::list<int> > foo = std::make_shared<qList<int> >(); // BÖSE!
    

    zu schreiben, weil std::list keinen virtuellen Destruktor hat. Man kann von Standardcontainern ableiten, und es wird auch funktionieren, aber die Benutzung eines solchen Konstrukts stellt höhere Anforderungen an die eigene Vorsicht, als man in aller Regel auf sich laden will.



  • Danke seldon,
    hier werden Sie wirklich geholfen. Ich habe jetzt echt was gelernt (UND verstanden 🙂 )

    Jetzt noch eine hoffentlich nicht zu dumme Frage, aber ich möchte eben C++ gleich richtig - also mit guten Stil - erlernen.

    Wie implementiere ich (stilvoll!) eine Liste, die einige spezielle Methoden dieser Liste erhält, aber wie eine std::list zu gebrauchen ist, ohne von dieser abzuleiten (also mit einer member variablen für diese List, also etwas in der Art:

    myList : public std::list<type>
    {
       void       SpecialFunction1();
       ....
    

    Aber ich müßte auch über die Listenelemente iterieren können, also z.B.

    void function irgendwas()
    {
       myList             liste;
       myList::iterator   it;
       .....
    
       for (it=liste.begin();  it!=liste.end(); it++)
       {
           // Tu halt was mit (*it)
           (*it)->.....
    

    Noch mal vielen Dank...



  • Machs halt im Prinzip so:

    template<typename T>
    class MyList
    {
    public:
       typedef typename std::list<T>::iterator Iterator;
    
    private:
       std::list<T> m_list;
    
    public:
       Iterator begin()
       {
          return m_list.begin();
       }
    };
    

    etc



  • Ethon schrieb:

    Machs halt im Prinzip so:
    ...
    etc

    Habe ich gemacht, Klappt super und der Code ist auch richtig übesichtlich und nachvollziebar.

    Vielen Dank an alle, für die Erleuchtung :p :p :p

    Aber eine letzte, allgemeine Frage habe ich noch.
    In Beispielen zu C++ sehe ich immer die Ausdrücke "foo" und **"bar".**Wie kommt es eigentlich dazu? Hat das irgendeine Bedeutung oder Herkunft oder historischen Hintergrund. Oder ist das einfach eben so??





  • Das dürfte in der Jargon-File ausführlich beschrieben werden.

    WARNUNG: Wenn du auf diesen Link klickst, wirst du viel Zeit verschwenden.



  • seldon schrieb:

    Du darfst jedenfalls nicht auf die Idee kommen, irgendwo

    std::shared_ptr<std::list<int> > foo = std::make_shared<qList<int> >(); // BÖSE!
    

    zu schreiben, weil std::list keinen virtuellen Destruktor hat.

    Doch, in genau diesem Fall geht ausnahmsweise alles gut, weil shared_ptr sich den richtigen Typen abspeichert. Das ist vom Standard garantiert.


Anmelden zum Antworten