Stream Operatoren mit Templates ueberladen geht nicht



  • Hallo,

    Ich versuche gerade die istream und ostream Operatoren zu ueberladen und per friend Deklaration zu nutzen. Ich moechte unbedingt auch Templates benutzen, jetzt habe ich ein Problem. Dazu habe ich folgendes Demo geschrieben, das compiliert soweit auch, nur:
    1. Bekomme ich Warnungen, dass die Friend-Declarations keine Template Funktionen in der Klasse deklarieren - ich komme einfach nur drauf, was ich da machen muss, ein operator<<<T>() sieht auch sehr seltsam aus fuer mich (abgesehn, das der gcc es nicht nimmt)?

    2. Bekomme ich einen Linker Error (wohl in Folge der falschen Deklarationen?)?

    3. Wuerde mich interessieren ob man die Allokation im Constructor so machen kann in C++? Da ich mom von C wuerde ich in C etwa schreiben:

    if(NULL == (pData = malloc(sizeof(xyz)))){ // Error-Behandlung
    

    da ich weiss das in C++ ein delete NULL moeglich ist, und ich vermeiden will teilweise allokierte Objekte zu erhalten, versuche ich in der catch Schleife dies anzudeuten (sollte mir mehr als Notiz dienen, als dass das hier wirklich Sinn macht), aber kann man das so machen?
    Danke im voraus!

    #include <iostream>
    #include <string>
    
    /*
      some class
    //*/
    template<class T>
    class SomeClass
    {
    private:
      T* pData;
    
    public:
      SomeClass(T t);
      ~SomeClass();
    
      void setData(T t);
      T getData();
    
      /*
        PROBLEM: friend declaration 
      //*/
      friend
      std::ostream& operator<<(std::ostream& output, SomeClass<T> sc);
    
      friend
      std::istream& operator>>(std::istream& input, SomeClass<T>& sc);
    };
    
    template<class T>
    SomeClass<T>::SomeClass(T t)
    {
      setData(t);
    }
    
    template<class T>
    SomeClass<T>::~SomeClass()
    {
      delete pData; pData = NULL;
    }
    
    template<class T>
    void SomeClass<T>::setData(T t)
    {
      delete pData; pData = NULL;
    
      // memory allocation
      try{
        if(NULL == (pData = new T)) throw "allocation failed!";
      }catch(std::string str){
        std::cerr << "ERROR: " << str << std::endl;
        delete pData; pData = NULL;
        return;
      }
    
      *pData = t;
    }
    
    template<class T>
    T SomeClass<T>::getData()
    {
      // check and return
      if(pData) return *pData;
      else return 0;
    }
    
    /*
      writes from the specified class into the output stream
    //*/
    template<class T>
    std::ostream& operator<<(std::ostream& output, SomeClass<T> sc)
    {    
      return output << sc.getData();
    }
    
    /*
      reads from the specified class into the input stream
    //*/
    template<class T>
    std::istream& operator>>(std::istream& input, SomeClass<T>& sc)
    {  
      // read the value out of sc into the istream object
      input >> sc.getData();
    
      // return the istream object
      return input;
    }
    
    /*
      some main()
    //*/
    int main()
    {
      // init
      SomeClass<std::string> sc = SomeClass<std::string>("Alec Eiffel");
    
      /* operator>>
      std::string str;
      str >> sc;
      std::cout << str << std::endl;
      //*/
    
      //* operator<<
      std::cout << sc << std::endl;
      //*/
    
      std::cout << "READY.\n";
      return 0;
    }
    


  • Also bei dem

    if(NULL == (pData = new T))
    

    musst du, wenn du es ohne exceptions machen willst ein new (nothrow) verwenden, da sonst nicht 0 zurückgegeben, sondern eine Exception geworfen wird.

    Außerdem sind deine Kommentare mit //*/ nicht sehr schön, der Comeau warnt zum Beispiel, weil das so wie du es schreibst verschachtelte Kommentare sind, die aber nicht erlaubt sind.

    Jetzt zu der Warnung mit der Template Funktion.

    Das Ganze funktioniert, wenn du die Template-Oerator-Überladung vorher deklarierst, also in etwa so:

    EDIT: Und dann die friend-Deklaration in der Klasse anpasst.

    //Forward declarations
    template<class T>
    class SomeClass;
    
    template<class T> 
    std::ostream& operator<<(std::ostream& output, SomeClass<T> sc) ;
    
    template<class T> 
    std::ostream& operator>>(std::ostream& output, SomeClass<T>& sc) ;
    
    template<class T> 
    class SomeClass 
    { 
    private: 
      T* pData; 
    
    public: 
      SomeClass(T t); 
      ~SomeClass(); 
    
      void setData(T t); 
      T getData(); 
    
      friend 
      std::ostream& operator<< <T>(std::ostream& output, SomeClass<T> sc); 
    
      friend 
      std::istream& operator>> <T>(std::istream& input, SomeClass<T>& sc); 
    };
    

    Felix



    1. ist das ziemlich viel Code, versuchs mal aufs wesentliche zu reduzieren (z.B. nur einer der beiden Operatoren), dann lesen sich das mehr Leute durch.

    2. ist "geht nicht" keine aufschlussreiche Fehlermeldung, zumindest so lange nicht bis die Glaskugeln wieder funktionieren

    3. wirft new standardmäßig selbst eine exception wenns mit der allokation nicht geklappt hat (vor über 10 Jahren war das mal anders, ich weiß...)

    4. wird bei deinem setData das Objekt erst standardkonstruiert und dann zugewiesen - die Benutzung des T-Copy-Ctors ist da besser (zumal der eher vorhanden ist als standard Ctor UND op=)

    5. könntest du statt eines Pointers einen smartpointer verwenden, dann wird setData zum Einzeiler - und die Exceptionsafety gibts gratis dazu.

    6) schonmal versucht das <T> bei someclass in der Friend-deklaration wegzulassen?



  • @Phoemuex
    Danke, das hat mir weitergeholfen. Leider haengt es mom noch woanders, der Destructor wird 2x aufgerufen, seltsamerweise. Danke auch fuer nothrow. Das mit den Kommentaren hab ich mir so angewoehnt und ich sehe da nicht wirklich das Problem. Mir dient sowas wie /// als termination fuer einen Block den ich per / oberhalb einfach auskommentieren kann oder per //* "einschalten" kann. Ist das schlecht? Wo ist das Problem?

    @Pumuckl
    Ohne jetzt hier Geflame anfangen zu wollen, ist meine Meinung zu Deinen Posts eine andere, ich werde / will diese hier jetzt nicht diskutieren:

    1. sehe ich nicht so, ich finde das ist das wesentliche, das so auch kompiliert werden kann. Imho ist es einfacher eine Funktion in einem minimalen Zusammenhang zu haben den ich auch gleich mitkompilieren kann, als den "Helfern" erstens die Lesbarkeit zu erschweren (man muss sich das Umfeld dazu denken, der Fehler kann dann auch nochmal irgendwo sein), als auch noch zweitens zu verlangen, dass ein Helfer sich den umgebenden Code selber dazu implementieren darf. Ausserdem denke ich nicht, dass die paar Zeilen Code jetzt nun sehr kompliziert zu lesen sind.

    2. "geht nicht" ist auch nicht die Fehlermeldung sondern der Titel, die Beschreibung befindet sich im Thread, dass ich diese (samt Code) nicht in die Titelzeile packen kann, sondern dort nur Schlagwoerter e.g. "Stream Operator", "ueberladen" und "Template" verwende versteht sich von selber. Vllt haette ich schreiben koennen "linked nicht" - ob sich deshalb aber jemand gleich denkt "JAAAaa, genau das hatte ich auch mal" und weiss auf Anhieb die Loesung, wage ich zu bezweifeln.

    3. Wie ich bereits gesagt habe, ist das in C anders, kA wie das bei C++ frueher war oder jetzt ist, ich bin von C ausgegangen, habe dies angegeben und DESHALB auch gefragt.

    4. Ja, es gibt einiges an dem Code zu verbessern! Ich hab auch noch ein paar Dinge gefunden und haenge noch an was anderem, aber so lerne ich eben auch.

    5. Nein kein Smartpointer, weil ich das Demo auch so geschrieben habe, damit ich diese Frage zur Allokation anbringen kann. Das ein Smartpointer das geschickter intern loest ist ja schoen, aber.. 🤡

    6. Jain, mir gings da auch mehr um die Schreibweise und ich wollte wissen ob es sowas wie "operator<<<T>" eigentlich ueberhaupt gibt. Ausserdem kam ich nicht darauf, dass er nicht linken kann, weil eine Forward Deklaration fehlt (scheint mir jedoch ein Klassiker zu sein).

    Danke Euch fuer Eure Antworten, ich hoffe ich finde das mit dem Destructor noch selber.



  • Fabeltier schrieb:

    @Phoemuex
    Das mit den Kommentaren hab ich mir so angewoehnt und ich sehe da nicht wirklich das Problem. Mir dient sowas wie /// als termination fuer einen Block den ich per / oberhalb einfach auskommentieren kann oder per //* "einschalten" kann. Ist das schlecht? Wo ist das Problem?

    Wenn du dich dran gewöhnt hast und dein Compiler sich nicht beschwert, ist es wahrscheinlich egal... Mich hat halt beim testen das

    "ComeauTest.c", line 2: warning: nested comment is not allowed
      //*/
    

    gestört.

    Das mit dem zweifachen Destruktor-Aufruf hört sich aber komisch an... Das sollte eigentlich nie passieren...

    Felix



  • zu 1): es kommt nicht drauf an dass der Code den du postest kompiliert werden kann. Die wenigsten hier werden es durch den Compiler schicken. Daher erhöht es deutlich die Lesbarkeit, wenn du den Code auf die Bereiche reduziest die nötig sind um deine Fragestellung zu verstehen (einen der op>>, die setData-methode und den Pointer in der Klasse). Den Ctor, Dtor sowie den zweiten op<< hättest du weglassen können, da Ctor und Dtor trivial sind und der op<< keine neuen Erkenntnisse liefert.
    2) Der genaue Wortlaut des Linkererrors (und evtl auch der Warnings) wäre schön gewesen - ein "ich hab da n Linkererror" ist fast genausowenig informativ wie "geht nicht"

    3-5 waren eben Antworten auf deine Frage 3, um dir zu sagen was man da in C++ besser machen kann - grade weil du von C kommst.
    Der Vollständigkeit halber noch den Code dazu (mit pData als std::auto_ptr<T*> oder boost::shared_ptr<T*>:

    template <class T> 
    void SomeClass<T>::setData(T const& t)
    {
      pData.reset(new T(t)) ;
    }
    


  • mein tipp: kein operator= definiert. ergo flache kopie. ergo "doppelter" dtor aufruf.

    Stichwort: law of the big three



  • Hi,
    Danke fuer den Tipp Shade of Mine - ich habe also noch einen operator= und einen Copyconstructor dazugeschrieben, das Problem blieb leider. Dann habe ich ueberlegt und habe folgendes geaendert:

    std::ostream& operator<<(std::ostream& output, SomeClass<T> sc)
    

    auf

    std::ostream& operator<<(std::ostream& output, SomeClass<T>& sc)
    

    Das hat geklappt. Das SomeClass<T> Object wurde einfach by-value uebergeben, weshalb eben der Destructor dafuer auch noch mal aufgerufen wurde, das ganze war schon eine "shallow copy" (weshalb ja ein operator= eig nciht noetig waere, also fuer's Demo). Dann hab ich im dtor mal experimentierweise statt delete, einfach den Wert von *pData neugesetzt. Beim zweiten Aufruf wurde der geaenderte Wert angezeigt. Also keine Deepcopy. Nja, der Hinweis gab mir die Richtung in die ich zu ueberlegen hatte, Danke 😉

    Weshalb deshalb allerdings das "delete" fehlschlaegt ist mir immer noch ein Raetsel, sollte es sich doch eigentlich dann beim zweiten mal schon um ein

    delete NULL;
    

    handeln?

    Ein weiteres Problem bleibt nun:
    ich will per

    SomeClass<std::string> sc = SomeClass<std::string>("");
    cin >> sc;
    

    einen Wert in "sc" einlesen lassen:

    template<class T>
    std::istream& operator>>(std::istream& input, SomeClass<T>& sc)
    {  
      input >> *(sc.pData);
      return input;
    }
    

    Mit meiner Implementierung per "friend" bekomme ich da nun allerdings Schwierigkeiten. Ich will operator>> aber dennoch weiterhin als "friend" deklariert haben und bekomme nun ein Problem:

    template<class T>
    std::istream& operator>>(std::istream& input, SomeClass<T>& sc)
    {  
      T t; // kann std::string, int, double, etc sein
      input >> t;
      sc.setData(t); // wohin mit dieser Zeile
      return input;
    }
    

    Der Aufruf sollte immer noch folgender sein:

    cin >> sc;
    

    Kann mir da noch jemand einen Tipp geben?
    Danke

    PS:
    @Pumuckl:
    zu 1. nja, wenn ich da als C++ Grobmotoriker einen Ctor, bzw Dtor schreibe, wer weiss ob der dann schon so "trivial" ist. Jeder haette vllt auch den Copy Ctor und operator= dazuimplementiert - ich eben nicht, manchmal liegts an sowas. Ja, wenn ich versuche die Fragen zu beantworten, jage ich das Zeug auch durch den Compiler (ok hab hier auch bisher nicht viel beantwortet 🤡 ).
    2) ok..
    Rest) ja, ok,..danke, hab die Allocation sinnvollerweise auch in den Ctor gehauen um setData() einfach nur als setData() zu verwenden, Baustellencode eben.


Anmelden zum Antworten