std::vector befüllen ohne copy constructor?


  • Mod

    alles andere dürfte nur Syntax Zucker sein, oder?

    range-based for?



  • Ja, sag ich doch.

    Das ist doch "nur" eine Kurzschreibweise für

    for(auto it = std::begin(cont); it != std::end(cont); ++it){}
    

    (oder ähnliches)
    oder verstehe ich da was falsch?

    Aber wo ist da durch den Standard definiert, dass es einen Performanceboost gibt?



  • Skym0sh0 schrieb:

    Einzig Move-Semantics und RValue-Referenzen bringen Performance**.

    **: Das ist eine harte Aussage, aber so nicht gemeint. C++11 bringt schon viel, aber ich zweifle an, dass eine komplette Refaktorisierung eines großen Codestücks so viel Nutzen bringt. RValues bringen der Performance noch was, aber alles andere dürfte nur Syntax Zucker sein, oder?

    Es war schon vorher in C++ möglich, verflixt schnelle Programme zu basteln. Es war auch schon in C möglich, sogar in Assembler. Allein es ist in C angenehmer als in Assembler, in altem C++ viel angenehmer als in C, und in C++11 (vor allem wegen range based for) angenehmer als in altem C++.



  • Ja, das stimmt.


  • Mod

    Skym0sh0 schrieb:

    (oder ähnliches)

    Und hier liegt der Hund begraben: Der end-Iterator wird bei deiner Schleife potenziell in jedem Durchlauf angefordert, während bei range-based for dieser (hier __end ) zwischengespeichert wird:

    {
        auto && __range = range-init;
        for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) {
            for-range-declaration = *__begin;
            statement
        }
    }
    

    Darüber hatten wir hier auch schon mal einen Thread.



  • @Arcoth: Stimmt. Trotzdem wählen Leute die Range-Based For primär wegen der Syntax. Dass dabei die Performance leicht besser sein kann, ist eher ein netter Nebeneffekt, vielen Entwicklern dürfte das gar nicht bekannt sein.



  • cooky451 schrieb:

    Ohne C++11 willst du eh kein C++ benutzen, dann nimm lieber [beliebige andere Sprache].

    So langsam muss man hier wirklich darauf achten, welche Antworten man ernst nehmen kann und welche nicht.



  • cl90 schrieb:

    Wie kann man einen std::vector befüllen ohne das ein copy constructor aufgerufen wird? möglichst ohne etwas auf den Heap zu laden.
    Ich würde gerne folgende Zeile, die noch einen copy constructor aufruft, austauschen.

    vector.push_back(QSubPlotable(&m_ColPattern, &m_FGWidthOffset))
    

    Wie immer: Mehr Information wär nett gewesen. Aber ich rate mal, dass QSubPlotable ein Typ und keine Funktion ist und dass Deine vector -Variable vom Typ vector<QSubPlotable> ist.

    Um etwas direkt in verschiedenen Containern zu erzeugen, ohne da etwas reinkopieren zu müssen, wurde in C++11 emplace hinzugefügt:

    vector.emplace_back(&m_ColPattern, &m_FGWidthOffset);
    

    Super wär's jetzt noch, wenn QSubPlotable movable würde (falls es das nicht schon ist). Dann könnte sich so ein Vektor auch relativ effizient vergrößern. Ansonnsten: Wenn du die Größe exakt kennst, kannst du natürlich auch vor den ganzen push/emplace_backs schon vector<>::reserve aufrufen.

    Wenn ein QSubPlotable jetzt etwas großes, unhandliches ist, was einen teuren Copy-Ctor und keinen Move-Ctor hat, könnte man es ja dann noch mit einer Indirektion versuchen, also unique_ptr<QSubPlotable> oder so.

    Auch wichtig: Den Kram messen, wenn es um Geschwindigkeit geht.



  • kkaw schrieb:

    cl90 schrieb:

    Wie kann man einen std::vector befüllen ohne das ein copy constructor aufgerufen wird? möglichst ohne etwas auf den Heap zu laden.
    Ich würde gerne folgende Zeile, die noch einen copy constructor aufruft, austauschen.

    vector.push_back(QSubPlotable(&m_ColPattern, &m_FGWidthOffset))
    

    Wie immer: Mehr Information wär nett gewesen. Aber ich rate mal, dass QSubPlotable ein Typ und keine Funktion ist und dass Deine vector -Variable vom Typ vector<QSubPlotable> ist.

    Um etwas direkt in verschiedenen Containern zu erzeugen, ohne da etwas reinkopieren zu müssen, wurde in C++11 emplace hinzugefügt:

    vector.emplace_back(&m_ColPattern, &m_FGWidthOffset);
    

    Super wär's jetzt noch, wenn QSubPlotable movable würde (falls es das nicht schon ist). Dann könnte sich so ein Vektor auch relativ effizient vergrößern. Ansonnsten: Wenn du die Größe exakt kennst, kannst du natürlich auch vor den ganzen push/emplace_backs schon vector<>::reserve aufrufen.

    Wenn ein QSubPlotable jetzt etwas großes, unhandliches ist, was einen teuren Copy-Ctor und keinen Move-Ctor hat, könnte man es ja dann noch mit einer Indirektion versuchen, also unique_ptr<QSubPlotable> oder so.

    Auch wichtig: Den Kram messen, wenn es um Geschwindigkeit geht.

    Ich hab es jetzt letztendlich auf dem Heap gelöst. also
    vector.pushback(new bla());
    mit entsprechenden deletes an anderen stellen.
    Danke für die Ausführung. Ich werde mir das zu einem späteren Zeitpunkt zusammen mit c++11 wieder ansehen.



  • cl90 schrieb:

    Ich hab es jetzt letztendlich auf dem Heap gelöst. also
    vector.pushback(new bla());
    mit entsprechenden deletes an anderen stellen.

    Och nö! Der Ansatz ist sehr fehleranfällig bzgl Speicherlecks. Dann nimm doch wenigstens boost::ptr_vector , wenn du schon keine C++11 Features benutzen willst/kannst.



  • kkaw schrieb:

    cl90 schrieb:

    Ich hab es jetzt letztendlich auf dem Heap gelöst. also
    vector.pushback(new bla());
    mit entsprechenden deletes an anderen stellen.

    Och nö! Der Ansatz ist sehr fehleranfällig bzgl Speicherlecks. Dann nimm doch wenigstens boost::ptr_vector , wenn du schon keine C++11 Features benutzen willst/kannst.

    Ich bin auch nicht zufrieden damit...
    Aber sag mir, kann ich mein Projekt ohne viel aufwand einfach auf C++11 umstellen?
    Ich habe jetzt schon so viele externe Abhängigkeiten und weit mehr als 5000 Zeilen code in über 80 files... Ich weiß nicht wie einfach man das umstellen kann.
    Ich habs mir vorerst nur auf meien ToCheck Liste für das nächste Projekt gesetzt.

    prt_vector regelt das delete selbst?
    Weil das wäre kein Problem. Ich hab Boost x64 bereits im Projekt.



  • Du musst nichts umstellen (ausser vielleicht den Compiler), C++11 ist abwärtskompatibel -- von unbedeutenden Features mal abgesehen.

    Ja, boost::ptr_vector räumt selbst auf. Ich persönlich bin aber spätestens seit C++11 kein Fan mehr von den Pointer-Containern, siehe hier.



  • ok dann werd ich mal lesen wie das geht mit dem umstieg.



  • Nexus schrieb:

    Ja, boost::ptr_vector räumt selbst auf. Ich persönlich bin aber spätestens seit C++11 kein Fan mehr von den Pointer-Containern, siehe hier.

    ...wobei ich im Falle von vector<unique_ptr<T>> die extra Indirektion, die man da durchführen muss, schon nervig finde. Das const wird auch leider nicht propagiert:

    void foo(vector<unique_ptr<int>> const& vec)
    {
      if (!vec.empty()) {
        *vec.front() += 17;  // klappt!
      }
    }
    

    Wenn Du "unhandliche Objekte" nur deswegen einzeln im Heap anlegst und die Adresse in einem unique_ptr speicherst, damit das Speichern im Vektor "effizienter" wird, ist diese Zeiger-Semantik eigentlich nicht das, was man haben will.

    Klar, ich kann mir ausgehend von vector<unique_ptr<T>> immer noch per boost::indirect_iterator zwei Iteratoren bauen, wo ich diese nervige extra Indirektion nicht mehr brauche und worüber auch ein const hinzugefügt werden kann. Das ist schon mal was.

    Aber vielleicht wäre da ein anderer Wrapper nicht ganz verkehrt. Ich habe in meiner Sammlung noch eine Kuh (copy-on-write). Hier nur mal eine vereinfachte und ungetestete Skizze davon:

    template<class T>
    class cow
    {
    public:
        cow()
        {
            static const auto def = make_shared<T>(); // cached default
            ptr = def;
        }
    
        cow(T const& x)
        : ptr(make_shared<T>(x))
        {}
    
        cow(T && x)
        : ptr(make_shared<T>(std::move(x)))
        {}
    
        template<class U, class...More
          ,class=typename std::enable_if<
            !std::is_convertible<U,cow const&>::value &&
            std::is_constructible<T,U,More...>::value
          >::type
        >
        cow(U && x, More && ... xx)
        : ptr(make_shared<T>(forward<U>(x),forward<More>(xx))
        {}
    
        T const& read() const {return *ptr;}
        T const& operator*() const {return *ptr;}
        T const* operator->() const {return ptr.get();}
        operator T const&() const& {return *ptr;}
        operator T const&() const&& = delete; // zu unsicher
    
        T& write()
        {
            if (!ptr.unique()) {
                ptr = make_shared<T>(*ptr);
            }
            assert(ptr.unique());
            return *ptr;
        }
    
    private:
        shared_ptr<T> ptr;
    };
    

    was man dann so nutzen könnte:

    vector<cow<unhandlicher_typ>> vec;
    vec.emplace_back(dies,und,jenes);
    ...
    for (auto& x : vec) {
        x.write().veraendere_dich(1,2,3);
    }
    for (unhandlicher_typ const& x : vec) {
        cout << x.get_something() << endl;
    }
    

    Und sonst könnte man ja auch einfach mal std::deque<T> mit emplace ausprobieren ohne unique_ptr und ohne cow . Da sollte auch nicht so viel kopiert werden. Und das wäre mein aktueller Tipp für cl90: Probier mal std::deque und/oder emplace aus.

    cl90 schrieb:

    ok dann werd ich mal lesen wie das geht mit dem umstieg.

    Ich glaube, beim Microsoft Compiler musst du gar nix besonderes machen. Er unterstützt automatisch schon C++11 Features, sofern einigermaßen aktuell. Beim GCC kommt es auf die Version an. Ggf brauchst du den Compilerschalter "-std=c++0x" oder "-std=c++11".


Anmelden zum Antworten