Template function für boost::ptr_vector<Foo> und std::vector<Foo*>



  • Hallo zusammen,

    eigentlich sind sich boost::ptr_vector und std::vector ja ziemlich ähnlich.
    Deswegen würde ich gerne eine Template-Funktion schreiben, die mit beiden umgehen kann. Bevor ich jetzt anfange irgendwelche Wrapper-Klassen zu schreiben, die den operator->() zur Verfügung stellen, frag ich hier mal, ob es eine einfache Lösung (mit Hilfe von boost?) gibt.

    Folgender Code liefert ja verständlicherweise unterschiedliche Ergebnisse.

    #include <boost/ptr_container/ptr_vector.hpp>
    #include <iostream>
    
    template<typename TList>
    void print(const TList& list){
            for(typename TList::const_iterator it = list.begin(); it != list.end(); ++it){
                    std::cout << *it << std::endl;
            }
    /*      for(typename TList::const_reference e : list){
                    std::cout << e << std::endl;
            }*/
    }
    
    int main(){
            boost::ptr_vector<int> bvec;
            bvec.push_back(new int(1));
            bvec.push_back(new int(2));
            bvec.push_back(new int(3));
            print(bvec);
            std::vector<int*> svec;
            svec.push_back(new int(1));
            svec.push_back(new int(2));
            svec.push_back(new int(3));
            print(svec);
    }
    

    1
    2
    3
    0x1002000b0
    0x1002000f0
    0x1002000e0

    Einmal iteriert er halt über die Referenzen, beim anderen mal über Zaiger.

    Der Code kann gerne wie ihr seht auch C++0x Features enthalten.
    Wie würdet ihr das lösen?

    Gruß,
    XSpille


  • Mod

    Was willst du erreichen? Wieso speicherst du in dem normalen vector Pointer? Der Sinn der Pointer-Container ist doch gerade, dass du in ihnen Pointer speichern kannst, die sich dann wie normale Werte verhalten. Und dann könntest du beide Container äquivalent benutzen.



  • Ich würde Funktionsüberladung nehmen, zumindest bei diesem Beispiel. Dinge wie is_pointer , SFINAE und sonstige Metaprogrammierung fände ich hier übertrieben.

    Übrigens hast du Memory Leaks 😉


  • Mod

    So könnte man das übrigens machen (Memory leaks korrigiere ich jetzt mal nicht). Ich finde das Vorhaben aber trotzdem komisch.

    #include <iostream>
    #include <boost/ptr_container/ptr_vector.hpp>
    
    using namespace std;
    
    template <typename T, typename value_type> struct print_impl
    {
      void operator()(const T& t)
      {
        cout<<"Pointer Container"<<endl;
        for(typename T::const_iterator it = t.begin(); it != t.end(); ++it){
          std::cout << *it << std::endl;
        }
      }
    };
    
    template <typename T, typename value_type> struct print_impl<T, value_type *>
    {
      void operator()(const T& t)
      {
        cout<<"Normaler Container mit Pointern"<<endl;
        for(typename T::const_iterator it = t.begin(); it != t.end(); ++it){
          std::cout << **it << std::endl;
        }
      }
    };
    
    template <typename T> void print(const T& t)
    {
      print_impl<T, typename T::iterator::value_type>()(t);
    }
    
    int main()
    {
            boost::ptr_vector<int> bvec;
            bvec.push_back(new int(1));
            bvec.push_back(new int(2));
            bvec.push_back(new int(3));
            std::vector<int*> svec;
            svec.push_back(new int(1));
            svec.push_back(new int(2));
            svec.push_back(new int(3));
    
            print(bvec);
            print(svec);
    
    }
    

    edit: Das geht natürlich nur so lange gut, wie du nicht Pointer auf Pointer oder höhere Indirektionen hast.



  • SeppJ schrieb:

    Wieso speicherst du in dem normalen vector Pointer?

    Bitte korrigier mich, aber einen ptr_vector kann ich doch nur verwenden, wenn er auch den Besitz des Zeigers kriegt, oder?

    Nexus schrieb:

    Übrigens hast du Memory Leaks 😉

    Ich weiß 🤡
    Wollte halt nur soviel Code hier posten, dass er kompilierbar ist 😉

    Bezüglich Funktionsüberladung/Templatespezialisierung:
    Ich dachte jetzt eigentlich an den Fall, dass man einen komplexen Algorithmus ausführen möchte. Bei Funktionsüberladung müsste ich alles 2mal implementieren.

    Angenommen ich habe einen ptr_vector und möchte den geringsten Unterschied zwischen zwei Elementen (wie auch immer definiert) ermitteln. Dann möchte ich auf einer Teilmenge davon die gleiche Operation ausführen. Was mache ich? Ich speichere mir die Pointer in einem Vektor. Der Besitz bleibt beim ptr_vector, der alle Elemente enthält. Oder?

    SeppJ schrieb:

    edit: Das geht natürlich nur so lange gut, wie du nicht Pointer auf Pointer oder höhere Indirektionen hast.

    Dessen bin ich mir bewusst 😉
    Gut, dass du es erwähnst, aber hast du schonmal einen ptr_vector<Foo*> verwendet?



  • XSpille schrieb:

    Hallo zusammen,

    eigentlich sind sich boost::ptr_vector und std::vector ja ziemlich ähnlich.
    Deswegen würde ich gerne eine Template-Funktion schreiben, die mit beiden umgehen kann.

    Das halte ich nicht für sinnvoll. Die Template-Funktion selbst sollte IMO nicht angepasst werden. T und T* sind halt nicht dasselbe. Ein Template zu schreiben das T und T* gleich behandelt ist daher IMO auch Quatsch. Was wenn T=int*, wie willst du das behandeln?

    D.h. du solltest das dort regeln, wo das Template aufgerufen wird.

    Bevor ich jetzt anfange irgendwelche Wrapper-Klassen zu schreiben, die den operator->() zur Verfügung stellen, frag ich hier mal, ob es eine einfache Lösung (mit Hilfe von boost?) gibt.

    Es gibt Boost.Lambda, damit kannst du "in place" einen Funktor erstellen der den übergebenen Parameter dereferenziert und an einen anderen Funktor weitergibt.

    Oder du kannst boost::make_indirect_iterator(base) verwenden um einen Iterator zu basteln der beim Dereferenzieren **base zurückgibt (also den übergebenen Iterator einmal zusätzlich dereferenziert).

    Findest du z.B. hier schön beschrieben:
    http://stackoverflow.com/questions/5437347/using-stl-algorithms-with-shared-ptr-function-objects/5440461#5440461

    p.S.: dazu musst du deine "print" Funktion natürlich umbauen. Entweder print(iterator begin, iterator end) , oder einen Funktor print(element const& e) machen, und den dann mit std::foreach aufrufen.



  • hustbaer schrieb:

    Das halte ich nicht für sinnvoll. Die Template-Funktion selbst sollte IMO nicht angepasst werden. T und T* sind halt nicht dasselbe. Ein Template zu schreiben das T und T* gleich behandelt ist daher IMO auch Quatsch. Was wenn T=int*, wie willst du das behandeln?

    Ich gebe dir recht, dass eine generelle Gleichbehandlung von T und T* quatsch ist, allerdings sind boost::ptr_vector<Foo> und std::vector<Foo*>, abgesehen von Besitzverhältnissen in meinen Augen ziemlich ähnlich...

    Ich werde zukünftig wohl boost::make_indirect_iterator verwenden, falls ein Iterator ausreichend ist:

    #include <boost/ptr_container/ptr_vector.hpp>
    #include <iostream>
    #include <boost/iterator/indirect_iterator.hpp>
    #include <algorithm>
    
    template<typename T>
    void print(T begin, T end){
            for(T it = begin; it != end; ++it){
                    std::cout << *it << std::endl;
            }
    }
    
    template<typename T>
    void print(const T& t){
            print(t.begin(), t.end());
    }
    
    int main(){
            boost::ptr_vector<int> bvec;
            bvec.push_back(new int(1));
            bvec.push_back(new int(2));
            bvec.push_back(new int(3));
            print(bvec);
            std::vector<int*> svec;
            for(int& i : bvec){ svec.push_back(&i); }
            print(boost::make_indirect_iterator(svec.begin()), boost::make_indirect_iterator(svec.end()));
    }
    

    Genau so etwas hab ich gesucht! THX hustbaer

    Hier noch eine boost-lambda-Version:

    #include <boost/ptr_container/ptr_vector.hpp>
    #include <iostream>
    #include <algorithm>
    #include <boost/lambda/lambda.hpp>
    #include <boost/lambda/bind.hpp>
    
    void print_element(int t){
            std::cout << t << std::endl;
    }
    
    template<typename T>
    void print(const T& t){
            std::for_each(t.begin(), t.end(), &print_element);
    }
    
    template<typename T>
    void print_pointer(const T& t){
            std::for_each(t.begin(), t.end(), boost::lambda::bind(print_element, *boost::lambda::_1));
    
    }
    int main(){
            boost::ptr_vector<int> bvec;
            bvec.push_back(new int(1));
            bvec.push_back(new int(2));
            bvec.push_back(new int(3));
            print(bvec);
            std::vector<int*> svec;
            for(int& i : bvec){ svec.push_back(&i); }
            print_pointer(svec);
    }
    

    DANKE EUCH ALLEN 👍

    @Nexus: Diesmal extra für dich ohne memory leaks 🙂



  • Eine Nachfrage noch:

    Brauch ich in folgendem Fall wirklich boost::remove_pointer?

    #include <boost/ptr_container/ptr_vector.hpp>
    #include <iostream>
    #include <algorithm>
    
    template<typename T>
    void print_element(const T& t){
            std::cout << t << std::endl;
    }
    
    template<typename T>
    void print(const T& t){
            std::for_each(t.begin(), t.end(), &print_element<typename boost::remove_pointer<typename T::value_type>::type>);
    }
    
    int main(){
            boost::ptr_vector<int> bvec;
            bvec.push_back(new int(1));
            bvec.push_back(new int(2));
            bvec.push_back(new int(3));
            print(bvec);
    }
    

    Gibt es keinen typedef für den eigentlichen Template-Parameter?
    (Wahrscheinlich schon 🤡 )


Anmelden zum Antworten