(friend)Template-Klassen und Vorwärtsdeklaration, ...
-
Hallo erstmal. Ein chönes Forum habt ihr hier.

Ich bereite mich momentan auf eine C++-Klausur vor und arbeite alte Klausur-Aufgaben durch. Dabei bin ich über folgende Aufgabe gestoßen:
Gegeben: Definitionen zweier Klassen Stack und Stack_el (Elemente des Stacks) mit einigen Feinheiten die ich hier nicht alle aufzählen möchte (Siehe Codebeispiel).
Aufgabe: Klassen in Template-Klassen umwandeln.
So weit bin ich gediehen:
#include<iostream> using namespace std; template <typename T> class Stack; template <typename T> class Stack_el{ private: T eintrag; Stack_el* next; public: Stack_el() { next=NULL; } template <typename T> friend class Stack; // Problem 1 }; template <typename T> class Stack{ private: Stack_el* data; // Problem 2 public: Stack(){ data=NULL; } void push(T); T pop(); }; int main(){ return 0; }Die Funktionen push und pop habe ich noch nicht geschrieben, weil erst einmal obiger Pointer (Problem 2) korrekt laufen müsste.
Soweit wie es hier steht (wenn ich die zwei Problemzeilen auskommentiere) kompiliert das Programm wenigstens schon mal einwandfrei.
Wie kann ich den Pointer und die friend-Deklaration hier ans laufen kriegen?
Wenn ich für die beiden Klassen einmal Typename T und einmal Typename S verwende läuft der Rahmen und die friend-Deklaration funktioniert, aber der Pointer immer noch nicht - und ist das überhaupt im Sinne des Erfinders (Aufgabenstellers)?Schöne Grüße,
Simon
-
template<T> friend class Stack;Ansonsten ist es üblich, StackElem als private innere Klasse:
class Stack { class StackElem { }; };Da StackElem nicht von außen verwendet wird, sondern nur intern.
-
Ja, das mag zwar üblich sein - aber in der Aufgabenstellung ist es leider so gefordert.
Den Pointer habe ich auch in den Griff bekommen:
#include<iostream> using namespace std; template <typename T> class Stack; template <typename T> class Stack_el{ private: T eintrag; Stack_el* next; public: Stack_el() { next=NULL; } Stack_el(T n){ eintrag = n; next=NULL; } template <T> friend class Stack; }; template <typename T> class Stack{ private: Stack_el<T*> data; public: Stack(){ data=NULL; } void push(T); T pop(); }; int main(){ return 0; }Nun zum nächsten Problem. Die Funktion push soll ich außerhalb des Klassenrumpfes definieren.
Normalerweise einfach, z.B. für int: void Stack::push(int neu){ ... }
Nun ist es aber ne Template-Klasse. Ich habe mir das hier zusammengebastelt:
template <typename T> void Stack<T>::push(T obj){ Stack_el<T> neu(obj); // Neues Element anlegen neu.next = data; // Zeiger auf (zuletzt oben gewesenes) Element setzen data = neu; // Neues Element liegt jetzt oben. }Unglaublich komplizierte Ansprache. Welchen Zweck erfüllt hierbei das <T> hinter Stack? Ohne kompiliert es nicht, aber ich wüsste gerne, warum ich das da hinschreiben muss.
Genau wie bei dem Pointer oben: Warum muss es Stack_el<T*> next heißen - und nicht Stack_el* next ?
-
Problem1: Du kannst nicht nocheinmal "typename T" schreiben, weil Du T schon verwendet hast. Einen Namen braucht man hier auch gar nicht.
template <typename> friend class Stack;müsste funktionieren.
Problem2: Stack_el bezeichnet keine Klasse sondern ein Klassentemplate. Die Klasse, die Du haben willst, ist Stack_el<T>. Damit ergibt sich
Stack_el<T>* next;Nur innerhalb des Klassemtemplates Stack_el kannst Du Stack_el als Abkürzung von Stack_el<T> verwenden. Das ist eine Sonderregel.
BlackSimon schrieb:
Stack_el<T*> data;Du meinst wohl Stack_el<T>*
template <typename T> void Stack<T>::push(T obj){ Stack_el<T> neu(obj); // Neues Element anlegen neu.next = data; // Zeiger auf (zuletzt oben gewesenes) Element setzen data = neu; // Neues Element liegt jetzt oben. }Such mal hier im Forum nach einem Zeiger-Tutorial. Hier sind ein paar Fehler drin. Das müsste
Stack_el<T>* neu = new Stack_el<T>(obj); neu->next = data; data = neu;heißen. Kann es sein, dass Du einen Java-Background hast? Es sieht so aus, als ob Dir nicht 100% klar wäre, dass in C++ Objekte "direkt" gehalten werden und eine Variable eines Klassentyps sich nicht wie eine Java-Referenz verhält.
BlackSimon schrieb:
Unglaublich komplizierte Ansprache. Welchen Zweck erfüllt hierbei das <T> hinter Stack?
Stack ist ein Template
Stack<foo> ist eine Klasse, die sich aus dem Template ergibt, wenn für T foo eingesetzt wird.BlackSimon schrieb:
Ohne kompiliert es nicht, aber ich wüsste gerne, warum ich das da hinschreiben muss.
Weil's ohne in dem Kontext keinen Typen bezeichnet (wie erforderlich) sondern ein Template.
BlackSimon schrieb:
Genau wie bei dem Pointer oben: Warum muss es Stack_el<T*> next heißen - und nicht Stack_el* next ?
Es muss Stack_el<T>* heißen, weil der Compiler nicht automatisch weiß, welche Klasse aus der Klassenfamilie Du meinst. Du könntest ja auch Stack_el<int> benutzen wollen. Lass Dich von den Namen der Typ-Parameter (T) nicht durcheinanderbringen. Das eine T (von Stack_el) hat mit dem anderen (von Stack) erstmal nichts zu tun.
Hier mal ein anderes Beispiel:
template<typename T> struct foo{}; template<typename T> struct bar{}; template<typename X, typename Y> class foobar { foo<X> * zeiger1; bar<Y> * zeiger2; };Woher sollte der Compiler, wenn Du <X> und <Y> in foobar weglässt, wissen, was das für Zeigertypen Du haben willst?
-
Danke dir, das hat mir im Verständnis schon einmal enorm weitergeholfen.
Tatsächlich war Java meine erste objektorientierte Programmiersprache, da fühl ich mich wesentlich wohler. Erstaunlich, dass du das aus dem Codefragment herauslesen kannst.
Nun habe ich noch ein Problem: Ich wollte den Stack mit einem Hauptprogramm testen, allerdings erhalte ich da wieder blöde Fehlermeldungen:
#include<iostream> using namespace std; template <typename T> class Stack; template <typename T> class Stack_el{ private: T eintrag; Stack_el* next; public: Stack_el() { next=NULL; } Stack_el(T n){ eintrag = n; next=NULL; } template <T> friend class Stack; }; template <typename T> class Stack{ private: Stack_el<T>* data; public: Stack(){ data=NULL; } void push(T); T pop(); }; template <typename T> void Stack<T>::push(T obj){ Stack_el<T>* neu = new Stack_el<T>(obj); neu->next = data; data = neu; } int main(){ Stack<int> s(); s->push(23); return 0; }
test.cpp: In function âint main()â:
test.cpp:40: error: request for member âpushâ in âsâ, which is of non-class type âStack<int> ()()âUnd zwar egal, wie ich die Funktion anspreche (s.push() s->push(), ... , s.push((int)), ...) da habe ich jetzt schon so einiges ausprobiert. Auch die Ausprägung Stack<int> kriege ich durch Ausprobieren nicht an eine passende Stelle eingefügt - das muss man wahrscheinlich schon wieder wissen wie es geht.

P.S.: Ein Pointer-/Zeiger-Tutorial habe ich mit der Board-Suche gesucht. Allerdings ist die dermaßen langsam (inklusive Timeouts) dass ich die richtige Kombination an Suchbegriffen noch nicht finden konnte.
-
BlackSimon schrieb:
Nun habe ich noch ein Problem: Ich wollte den Stack mit einem Hauptprogramm testen, allerdings erhalte ich da wieder blöde Fehlermeldungen:
Wäre gut, wenn du nächstes Mal die zugehörige Zeile angeben könntest (40 stimmt ja offensichtlich nicht mit deinem Code überein). Was möchtest du hiermit erreichen?
template <T> friend class Stack;Wenn du willst, dass die Klasse zu den Klassen befreundet ist, welche durch Einsetzen des gleichen Templateparameters in das
Stack-Template entstehen, kannst du das folgendermassen bewerkstelligen. Schliesslich istTdann schon gegeben.friend class Stack<T>;BlackSimon schrieb:
P.S.: Ein Pointer-/Zeiger-Tutorial habe ich mit der Board-Suche gesucht. Allerdings ist die dermaßen langsam (inklusive Timeouts) dass ich die richtige Kombination an Suchbegriffen noch nicht finden konnte.
-
int main(){ Stack<int> s(); // 1 s->push(23); // 2 return 0; // 3 }- Du deklarierst da eine Funktion!
- s soll ein normales Objekt auf dem "Stack" (also nicht im Freispeicher) werden, da ist operator-> fehl am Platz.
- return 0 ist überflüssig, lass es einfach weg!
Damit kommen wir zur korrigierten Fassung:
int main(){ Stack<int> s; s.push(23); }
-
Nexus schrieb:
BlackSimon schrieb:
Nun habe ich noch ein Problem: Ich wollte den Stack mit einem Hauptprogramm testen, allerdings erhalte ich da wieder blöde Fehlermeldungen:
Wäre gut, wenn du nächstes Mal die zugehörige Zeile angeben könntest (40 stimmt ja offensichtlich nicht mit deinem Code überein). Was möchtest du hiermit erreichen?
Entschuldige, ich hatte auskommentierte Zeilen aus dem Code hier gelöscht ohne die Zeile der Fehlermeldung anzupassen. War doof...
Nexus schrieb:
BlackSimon schrieb:
P.S.: Ein Pointer-/Zeiger-Tutorial habe ich mit der Board-Suche gesucht. Allerdings ist die dermaßen langsam (inklusive Timeouts) dass ich die richtige Kombination an Suchbegriffen noch nicht finden konnte.
Herrlich, danke! Da werde ich mich in den nächsten Stunden und Tagen noch einmal intensiv durcharbeiten.

l'abra d'or schrieb:
int main(){ Stack<int> s(); // 1 s->push(23); // 2 return 0; // 3 }- Du deklarierst da eine Funktion!
- s soll ein normales Objekt auf dem "Stack" (also nicht im Freispeicher) werden, da ist operator-> fehl am Platz.
- return 0 ist überflüssig, lass es einfach weg!
Damit kommen wir zur korrigierten Fassung:
int main(){ Stack<int> s; s.push(23); }Hmm.
Zu 1) Eigentlich rufe ich da den (Standard-)Konstruktor der Klasse Stack auf, wie er weiter oben definiert wurde.
Wenn ich das in deinen Korrekturvorschlag anpasse, läuft der Compiler Amok:test.cpp: In instantiation of âStack_el<int>â:
test.cpp:22: instantiated from âvoid Stack<T>::push(T) [with T = int]â
test.cpp:28: instantiated from here
test.cpp:3: error: template parameter âclass Tâ
test.cpp:7: error: redeclared here as âint <anonymous>âZu 2) Siehe Pointer-Tutorial das ich noch vor mir habe. Da hatte ich zuerst s.push(23) stehen. ^^
zu 3) return 0 überflüssig? Das haben mir meine Übungsleiter anders eingedrillt. Ein Weglassen lässt mir er Compiler aber tatsächlich durchgehen, hätte ich nicht gedacht.

In der Klausur werd ichs trotzdem sicherheitshalber mal notieren, falls ich überhaupt eine Hauptfunktion schreiben muss (sehr unwahrscheinlich).Danke soweit!
---
Wenn ich jetzt noch Nexus Tipp zur Friend-Deklaration umsetze funktioniert der Code aus deinem Verbesserungsvorschlag einwandfrei!
Aber warum genau Stack<int> s; statt Stack<int> s() oder Stack <int> s = new Stack<int>()?

-
BlackSimon schrieb:
Aber warum genau Stack<int> s; statt Stack<int> s() oder Stack <int> s = new Stack<int>()?

Aus dem gleichen Grund wie hier:
int i; int i(); int i* = new int();Abgesehen davon, dass der Code ohne * in der 3. Zeile nicht kompiliert, sind das drei verschiedene Dinge. Der Reihenfolge nach:
1. Deklaration/Definition einer Variable mit automatischem Speicherbereich ("Stack")
2. Deklaration einer Funktion mit Rückgabetypintund keinen Parametern
3. Deklaration/Definition eines Zeigers, der auf einen dynamisch allokiertenintverweist. Freigabe ist manuell überdeleteerforderlich.
-
Die Lösung bringt dann der letzte Post von Nexus:
friend class Stack<T>;Wenn man sich das genau überlegt ist es auch klar warum.
Da hab ich dir vorher Mist erzählt, hatte selber noch nie das Vergnügen mit friend template classes.Es wird auch vollkommen unnötig wenn man StackEl als innere Klasse im private-Bereich anlegt. Da macht man alle Member public (oder man nimmt gleich struct), und die Sache ist gegessen

-
Was ist eigentlich ein El? Ist es etwas von hier? :p
Ich meine damit nur, dass man sich für solche Dinge schon vernünftige Bezeichner suchen darf. Zumindest "Elem" ist wohl angebracht, dafür kann man den unnötigen Unterstrich weglassen (also "StackElem").
