Schweres Objekt in Map einfügen



  • Ansatz 3, der auf Ansatz 2 aufbaut:

    boost::ptr_map<ID, HeavyObject> map;
    
    std::unique_ptr<HeavyObject> h(new HeavyObject);
    if ( !h->load() )
        throw std::runtime_error("load failed");
    
    map.insert( id, h.release() );
    


  • Von wieviel Kilogramm sprechen wir hier?


  • Mod

    mit emplace / emplace_hint (und einem geeigneten Construktor von HeavyObject).

    std::map<ID, HeavyObject> map;
    
    auto pair_ = map.emplace(id, arg); // Mit Heavy-ObjectConstruktor, der ein Argument arg entgegen nimmt, sonst
    auot pair_ = map.emplace(std::piecewise_construct, std::make_tuple(id), std::make_tuple(args...)); // mit mehr/0 Argumenten.
    


  • Übrigens wird im Ansatz 1 sogar öfter kopiert:

    auto pair = map.insert(std::make_pair(id, HeavyObject())); /// Zeile 3
    

    Und Ideone gibt mir vier: http://ideone.com/OVU5lu



  • camper, vielen Dank, emplace() muss ich mir wirklich merken. Leider gibts hier keinen vernünftigen Konstruktor, d.h. ich müsste nach emplace() wieder Ansatz 1 betreiben.

    Ausserdem kommt man bei std::map nicht darum herum, jeweils ein std::pair zu übergeben... Gerade mit C++11 hätte ich mir erhofft, dass es für insert() eine schönere Schnittstelle gäbe. Gibts einen speziellen Grund, wieso man nicht 2 Parameter für key und value haben kann? Jetzt mit Move-Semantik sollte doch Exceptionsicherheit besser in den Griff zu kriegen sein...

    Sone, ich kann hier kein Boost verwenden, und defaultkonstruierte Objekte sind billig zu kopieren.

    <:xmas2:>


  • Mod

    Sone schrieb:

    Übrigens wird im Ansatz 1 sogar öfter kopiert:

    auto pair = map.insert(std::make_pair(id, HeavyObject())); /// Zeile 3
    

    Und Ideone gibt mir vier: http://ideone.com/OVU5lu

    Das ist wahrscheinlich ein Bug der Standardbibkliothek, kann ich jedenfalls nur mit g++ 4.5 und std=c++0x reproduzieren (theoretisch sind nat. 4 Kopien möglich - davon ist die beim return in make_pair aber trivial zu vermeiden).
    In C++03 sollten es immer genau 3 Kopien sein.
    Ab g++ 4.7 sind es nur noch die erwarteten 2 Kopien (dank des Template-pair-inserts).


  • Mod

    glühnase schrieb:

    camper, vielen Dank, emplace() muss ich mir wirklich merken. Leider gibts hier keinen vernünftigen Konstruktor, d.h. ich müsste nach emplace() wieder Ansatz 1 betreiben.

    Ausserdem kommt man bei std::map nicht darum herum, jeweils ein std::pair zu übergeben... Gerade mit C++11 hätte ich mir erhofft, dass es für insert() eine schönere Schnittstelle gäbe. Gibts einen speziellen Grund, wieso man nicht 2 Parameter für key und value haben kann? Jetzt mit Move-Semantik sollte doch Exceptionsicherheit besser in den Griff zu kriegen sein...

    Die schönere Schnittstelle ist emplace (und die nimmt deine zwei Argumente an)...

    map.emplace(id, HeavyObject());
    

    Das zugrundeliegende Problem ist hier die 2-Stufen-Konstruktion des Objektes... sofern du HeavyObject nicht verändern kannst besteht natürlich immer noch die Möglichkeit, dieses in einem Wrapper zu verpacken, der den Aufruf von Load gleich bei der Konstruktion vornimmt.



  • Ah, das ginge sogar, sehr schön.
    Leider implementiert VC++10 nur map::emplace() mit einem Parameter.

    Vielen Dank nochmals und schöne restliche Festtage.

    <:xmas2:>


  • Mod

    camper schrieb:

    Sone schrieb:

    Übrigens wird im Ansatz 1 sogar öfter kopiert:

    auto pair = map.insert(std::make_pair(id, HeavyObject())); /// Zeile 3
    

    Und Ideone gibt mir vier: http://ideone.com/OVU5lu

    Das ist wahrscheinlich ein Bug der Standardbibkliothek, kann ich jedenfalls nur mit g++ 4.5 und std=c++0x reproduzieren (theoretisch sind nat. 4 Kopien möglich - davon ist die beim return in make_pair aber trivial zu vermeiden).
    In C++03 sollten es immer genau 3 Kopien sein.
    Ab g++ 4.7 sind es nur noch die erwarteten 2 Kopien (dank des Template-pair-inserts).

    Nach ein bisschen Experimentieren stelle ich fest, das der Fehler offenbar doch woanders liegt:
    Weder g++ 4.5 noch g++ 4.6 führen bei std::pair im c++0x normale RVO durch (jedenfalls nicht mit so einem Membertyp), was beim Aufruf von make_pair 2 zusätzliche Kopien erzeugt. Bei g++ 4.6 fällt das nicht so auf, weil dort dafür dank des template-inserts beim insert nur noch eine Kopie entsteht.



  • 1. Kopie: Funktionsparameter von make_pair wird mit der Temporary initialisiert
    2. Kopie: Das HeavyObject im zurückgegebenen pair wird mit dem Funktionsparameter initialisiert.
    3. Kopie: Das HeavyObject im Funktionsparameter vom insert wird mit dem HeavyObject aus dem Rückgabewert von make_pair initialisiert.
    4. Kopie: insert wird für den internen Baum den Funktionsparameter, das pair , erneut kopieren.

    Soweit richtig?


  • Mod

    Sone schrieb:

    1. Kopie: Funktionsparameter von make_pair wird mit der Temporary initialisiert
    2. Kopie: Das HeavyObject im zurückgegebenen pair wird mit dem Funktionsparameter initialisiert.
    3. Kopie: Das HeavyObject im Funktionsparameter vom insert wird mit dem HeavyObject aus dem Rückgabewert von make_pair initialisiert.
    4. Kopie: insert wird für den internen Baum den Funktionsparameter, das pair , erneut kopieren.

    Soweit richtig?

    Make_pair hat Referenzparameter, dort entsteht keine Kopie. g++ vor 4.6 führt innerhalb von insert 2 Kopien durch, warum auch immer.
    Nach ein bisschen mehr Experimentieren habe ich das Problem reduzieren können:

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    struct foo
    {
        typedef T first_type;
        T value;
    
        foo(const foo&) = default;
        foo(foo&&) = default;
    
        foo() : value() {}
    
        foo(const T& x) : value(x) {}
    
        template<class U>
        foo(U&& x) : value(std::forward<U>(x)) {}
    
        template<class U,
            typename std::enable_if<std::is_convertible<U,T>::value, int>::type = 0>
        foo(foo<U>&& p) : value(std::forward<U>(p.value))
        {
            std::cout << "Template-foo-move\n";
        }
    
    };
    
    template <typename T>
    foo<typename std::decay<T>::type>
    make_foo(T&& t)
    {
        return foo<typename std::decay<T>::type>(std::forward<T>(t));
    }
    
    struct bar
    {
        bar() = default;
        bar(const bar&) { std::cout << "Copy-ctor!\n"; }
    };
    
    int main()
    {
        auto x = make_foo((bar()));
    }
    

    Ausgabe mit g++4.7.2 oder 4.8 oder clang:

    Copy-ctor!
    

    In g++ vor 4.7 muss aber die defaulted-Definiton des Move-Konstruktors weg (führt zum Fehler). Implizit wird dieser Move-Konstruktor aber nicht deklariert, und das macht dann plötzlich den Templatekonstruktor zu einer passenden Wahl, wenn foo gemoved werden sollte. Weil das aber kein Move-Konstruktor ist, kann er auch nicht eliminiert werden.
    Ausgabe ohne defaulted-Move:

    Copy-ctor!
    Copy-ctor!
    Template-foo-move
    Copy-ctor!
    Template-foo-move
    

    /usr/lib64/gcc/x86_64-pc-linux-gnu/4.6.3/include/g++-v4/bits/stl_pair.h schrieb:

    #ifdef __GXX_EXPERIMENTAL_CXX0X__
          constexpr pair(const pair&) = default;
    
          // Implicit.
          // pair(pair&&) = default;
    

    Dieser Kommentar ist offensichtlich falsch. Wenn ich mit g++ 4.5 oder 4.6 den rvalue-pair-Templatekonstruktor entferne, wird der Copy-Konstruktor ausgewählt und der kann dann eliminiert werden.



  • :augen glänzen:
    Eine letzte Frage hätte ich noch:

    g++ vor 4.6 führt innerhalb von insert 2 Kopien durch, warum auch immer.

    Darf er das überhaupt?



  • Hier noch mal der Standard gegen das was im oben zitierten Kommentar steht:

    N3337 §12.8 / 9 schrieb:

    If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
    — X does not have a user-declared copy constructor,


  • Mod

    sone_logoff schrieb:

    :augen glänzen:
    Eine letzte Frage hätte ich noch:

    g++ vor 4.6 führt innerhalb von insert 2 Kopien durch, warum auch immer.

    Darf er das überhaupt?

    Zitat habe ich keines und auch keine Lust zu suchen. Zumindest in C++03 scheint mir das ein reines QoI-Problem zu sein, also denke ich mal, dass es zumindest dort erlaubt ist.



  • Mir ist da noch etwas aufgefallen. Ich wollte es eigentlich mit dem unique_ptr lösen (Ansatz 2).

    Allerdings ist das relativ ungünstig, falls die ID schon in der Map gespeichert ist. Dann wird das HeavyObject trotzdem geladen, obwohl es nachher nicht eingefügt werden kann. Wahrscheinlich kommt so ein Fehler zwar nicht häufig vor, aber würdet ihr den noch separat checken?

    <:xmas2:>



  • Ja, klar, prüf vorher ob es den Key schon gibt.
    Ist ja kein echter Aufwand.


Anmelden zum Antworten