Template Problem



  • Hallo,
    ich arbeite zum ersten mal mit Templates und wollte zum Probieren
    mal eine einfache verkettete Liste programmieren. Jetzt meldet
    mir der Compiler einen Fehler und ich kann ihn einfach nicht finden.
    Ich schätze, das Problem hängt mit der inneren Klasse zusammen.
    Kann mir jemand helfen?!

    Gruß Ronny

    Hier der Quellcode:

    #include <stdio.h>
    
    template <class T>
    class Liste
    {
    	public:
    
    	Liste();
    	void add( T &value );
    
    	private:
    	class Listenknoten {
    		public:
    		Listenknoten( T *value, Listenknoten *ne );
    
    		T* val;
    		Listenknoten* pnext;
    	};
    
    	Listenknoten *first;
    
    };
    
    template <class T>
    Liste<T>::Liste()
    {
    
    }
    
    template <class T>
    void Liste<T>::add( T &value )
    {
    	// Zeile mal auskommentieren
    	new Listenknoten( &value, NULL );
    
    }
    
    template <class T>
    Liste<T>::Listenknoten::Listenknoten( T *value, Listenknoten *ne )
    {
    	val = value;
    	pnext = ne;
    }
    
    int main(){
    	Liste<int> li;
    
    	int a=3;
    	li.add( a );
    
     return 0;
    }
    


  • Dazu wäre es nicht schlecht, den Compilerfehler mal zu sehen ...



  • Linker-Vorgang läuft...
    Template.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: __thiscall Liste<int>::Listenknoten::Listenknoten(int *,class Liste<int>::Listenknoten *)" (??0Listenknoten@?$Liste@H@@QAE@PAHPAV01@@Z)
    Debug/Templates.exe : fatal error LNK1120: 1 unaufgeloeste externe Verweise
    Fehler beim Ausführen von link.exe.



  • Der Fehler hat was mit der Schachtelung von templates zu tun. So funktioniert's:

    #include <new>
    #include <iostream>
    
    using namespace std;
    
    template< typename T >
    class list_t {
    
        private:
    
            template< typename T >
            class node_t {
    
                public:
    
                    template< typename T >
                    node_t( T &value ) : m_value( value ), m_next( 0 ) { }
    
                    void g( );
    
                    T m_value;
                    node_t< T > *m_next;
            };
    
        public:
    
            list_t( );
            bool empty( );
            size_t size( );
            T begin( );
            T end( );
            T at( size_t index );
            void push_front( const T &value ) throw( ... );
            void push_back( const T &value ) throw( ... );	
            void pop_front( );
            void pop_back( );
    
        private:
    
            node_t< T > *m_first;
            node_t< T > *m_end;
            size_t m_size;
    };
    
    // das würde funktionieren, da die funktion selbst keinen
    // Template-Parameter benötigt.
    template< typename T >
    void list_t< T >::node_t< T >::g( )
    {
    }
    
    template< typename T >
    list_t< T >::list_t( ) : m_first( 0 ), m_end( 0 ), m_size( 0 )
    {
    }
    
    template< typename T >
    bool list_t< T >::empty( )
    {
        return m_first == 0;
    }
    
    template< typename T >
    size_t list_t< T >::size( )
    {
        return m_size;
    }
    
    template< typename T >
    T list_t< T >::begin( )
    {
        return m_first->m_value;
    }
    
    template< typename T >
    T list_t< T >::end( )
    {
        return m_end->m_value;
    }
    
    template< typename T >
    void list_t< T >::push_front( const T &value ) throw( ... )
    {
        node_t< T > *tmp;
    
        try {
            tmp = new node_t< T >( value );
        } catch( std::bad_alloc &ba ) {
            throw &ba;
        }
    
        ++m_size;
    
        if( !m_first ) {
            m_first = tmp;
            return;
        }
    
        tmp->m_next = m_first;
        m_first = tmp;
    }
    
    template< typename T >
    void list_t< T >::push_back( const T &value ) throw( ... )
    {
        node_t< T > *tmp;
    
        try {
            tmp = new node_t< T >( value );
        } catch( std::bad_alloc &ba ) {
            throw( ba );
        }
    
        ++m_size;
    
        if( !m_first ) {
            m_end = m_first = tmp;
            return;
        }
    
        node_t< T > *cur = m_first;
    
        while( cur->m_next )
            cur = cur->m_next;
    
        m_end = cur->m_next = tmp;
    }
    
    template< typename T >
    void list_t< T >::pop_front( )
    {
        if( m_size == 1 ) {
    
            delete m_first;
            m_first = 0;
            m_size = 0;
    
        } else if( m_size > 1 ) {
    
            node_t< T > *tmp = m_first->m_next;
            delete m_first;
            m_first = tmp;
            --m_size;
        }
    }
    
    template< typename T >
    void list_t< T >::pop_back( )
    {
        if( m_size == 1 ) {
    
            delete m_first;
            m_end = 0;
            --m_size;
    
        } else if( m_size > 1 ) {
    
            node_t< T > *cur = m_first;	
    
            while( cur->m_next->m_next )
                cur = cur->m_next;
    
            delete cur->m_next;
            m_end = cur;
        }
    }
    
    template< typename T >
    T list_t< T >::at( size_t index )
    {
        if( index < m_size ) {
    
            if( index == 0 )
                return m_first->m_value;
    
            if( index == ( m_size - 1 ) ) {
                return m_end->m_value;
    
            node_t< T > *cur = m_first->m_next;
    
            for( size_t i = 1; i < index; ++i )
                cur = cur->m_next;
    
            return cur->m_value;
        }
        return 0;
    }
    
    int main( )
    {
        list_t< int > list;
    
        list.push_back( 1 );
        list.push_back( 2 );
        list.push_back( 3 );
    
        cout << list.begin( ) << endl;
        cout << list.end( ) << endl;
    
        list.push_front( 0 );
        cout << list.begin( ) << endl;
    
        list.pop_front( );
        cout << list.begin( ) << endl;
    
        cout << list.at( 0 ) << endl;
        cout << list.at( 1 ) << endl;
        cout << list.at( 2 ) << endl;
    
        list.push_back( 3 );
        cout << list.at( 3 ) << endl;
    }
    

    Greetz, Swordfish

    PS: Viel Spaß bei'm erweitern... 😉



  • Vielen Dank für die Antwort. Habe mein Programm ein wenig umgebaut.
    Scheinbar funktioniert es nur wenn der Konstruktor der inneren Klasse
    gleich in der Deklaration initialisiert wird.
    Also ich hätte sonst in der Klasse Listnode den Konstruktor deklariert,
    einfach nur mit

    Listnode( T *val );
    

    und dann die Implementierung separat vorgenommen.

    template <typename T>
    Liste<T>::Listenknoten<T>::Listenknoten( T *value ):
    	val( value ), pnext( NULL );
    {
    }
    

    So ganz verstehe ich aber nicht, wieso meine Variante nicht funktioniert.
    Kann mir das jemand erklären?

    #include <stdio.h>
    
    template <typename T>
    class Liste {
    
    private:
    
    	//////////////
    	template <typename T>
    	class Listenknoten {
    
    	public:
    
    		template <typename T>
    		Listenknoten( T *value ) : val(value),pnext(NULL)
    		{
    		}
    
    		T *val;
    		Listenknoten<T> *pnext ;
    	};
            //////////////
    
    	Listenknoten<T> *first;
    
    public:
    
    	Liste();
    	void add( T &value );
    };
    
    template <typename T>
    Liste<T>::Liste() : first( NULL )
    {
    }
    
    template <typename T>
    void Liste<T>::add( T &value )
    {
    	// hier Fehler
    	new Listenknoten<T>( &value );
    }
    
    int main(){
    	Liste<int> li;
    
    	int a=3;
    	li.add( a );
    
     return 0;
    }
    


  • Versuchs mal damit:

    template <typename T> 
    void Liste<T>::add( T &value ) 
    {   
    new Liste<T>::Listenknoten<T>( &value ); 
    }
    


  • Ansonsten stellt sich mir die Frage weshalb du eine Klasse innerhalb einer anderen definierst. Die innere Klasse wird nach Außen gegeben und dann hat man im Code irgendwann solche schönen Bezeichner-Orgien:

    namespace::class::class:: ... ::function

    ...oder man bemüht wieder typedefs um die Sache dann wieder einfacher zu machen... was den Code natürlich viel lesbarer macht. *hust*



  • Gute Idee aber leider funktioniert das auch nicht.
    Aber recht hast Du eigentlich, man könnte das ohne innere
    Klasse machen. Wahrscheinlich würde es dann gehen.

    Ok, vielen Dank für die Antworten!



  • Mathias schrieb:

    Ansonsten stellt sich mir die Frage weshalb du eine Klasse innerhalb einer anderen definierst.

    Bei einer Liste bietet es sich schon an, den Node als nested Klasse zu implementieren. Das ist nichts Ungewöhnliches.

    Mathias schrieb:

    Die innere Klasse wird nach Außen gegeben

    Wieso nach Aussen? Der Client sollte den Node auf jeden Fall nie zu Gesicht bekommen. Nur innerhalb der Liste ist er von Bedeutung.

    #include <stdio.h>
    
    template <typename T>
    class Liste {
    
    private:
    
    	//////////////
    	template <typename T>
    	class Listenknoten {
    

    IIRC dürfte das kein gültiger Code sein. Mal abgesehen davon, dass es <cstdio> sein muss, wird hier zweimal der Template Parameter T definiert. Stichwort: Shadow Template Parameter. Zudem muss Listenknoten kein Template sein, immerhin kennt die Klasse den Elementtyp.



  • @groovemaster
    Ich hab ja auch nicht gesagt, dass es etwas ungewöhnliches ist oder dass man es nicht tun darf.
    Ich meinte nur, dass sich daraus ziemlich lange Bezeichner ergeben.

    Ansonsten hast du recht die Knoten in dem Beispiel ist die Knotenklasse private, so dass es sich an der Stelle hier nicht auf die Schnittstelle der Liste auswirkt.

    Wieso nach Aussen? Der Client sollte den Node auf jeden Fall nie zu Gesicht bekommen. Nur innerhalb der Liste ist er von Bedeutung.

    Wir reden von zwei unterschiedlichen Dingen. Das eine ist die Schnittstelle der Liste und das andere ist der Zugriff auf die Node-Klasse. Was spricht dagegen die Node-Klasse noch an anderer Stelle zu nutzen, auch wenn ich nicht möchte, dass Node-Objekte über die Schnittstelle der Liste übergeben werden?



  • Mathias schrieb:

    Wir reden von zwei unterschiedlichen Dingen. Das eine ist die Schnittstelle der Liste und das andere ist der Zugriff auf die Node-Klasse.

    Ich habe aber nur über die Schnittstelle gesprochen. Der Zugriff ist doch praktisch gleich, lediglich mit dem Unterschied, dass der Node entweder einen Scope höher oder niedriger zu finden ist. Das ist aber nicht deine Aufgabe, sondern die des Compilers. Ansonsten gibt es lediglich Unterschiede im Detail.

    Mathias schrieb:

    Was spricht dagegen die Node-Klasse noch an anderer Stelle zu nutzen, auch wenn ich nicht möchte, dass Node-Objekte über die Schnittstelle der Liste übergeben werden?

    Nichts. Das kannst du letztendlich machen, wie du lustig bist. Nur sind solche Node Klassen nicht gerade prädestiniert für universellen Einsatz. ZB sieht man die Unterschiede schon am Beispiel einer Liste. Single Linked oder Double Linked? Damit ändert sich schon die Implementation. Klar, du kannst alles umgehen, zB mit Vererbung. Fragt sich nur, ob der Kosten-Nutzen Faktor vertretbar ist. Wie auch immer, alles was ich gesagt habe, war, dass hier eine nested Klasse gängige Praxis ist. Nicht mehr und nicht weniger.


Anmelden zum Antworten