std::vector Frage


  • Mod

    Jodocus schrieb:

    SeppJ schrieb:

    Arcoth schrieb:

    manni66 schrieb:

    Arcoth schrieb:

    In meiner Demonstration habe ich einfach flüchtig ein Array statt einem vector genommen, was einen Default-Konstruktor benötig.

    Und mit einem vector würde es ohne Defaultkonstruktor funktionieren?

    😕

    std::vector<Example> vec(0, 10);
    std::iota(std::begin(e), std::end(e), 0);
    

    😕
    Sprich: Nein, es würde nicht funktionieren? Ob du nun implizit ein Array mit 0 initialisierst oder einen Vector explizit mit 0, macht doch dein Beispiel nicht besser. In jedem Fall legst du unnötige, und ausdrücklich nicht gewünschte Defaultobjekte an, bevor du die korrekten Objekte erzeugst.

    Ich stimme prinzipiell zu, allerdings hat der OP klargemacht, dass er solche Objekte wegen ihres Destruktors vermeiden möchte - ein Destruktor wird hier aber so wie ich das sehe nirgends aufgerufen, wodurch Arcoth's Lösung technisch gesehen schon den Anforderungen genügen würde.

    Wenn der Konstruktor tatsächlich Ressourcen belegt, was der Beschreibung zufolge anzunehmen ist, dann muss eine Zuweisung die überschriebene Ressource auch wieder freigeben (weshalb man in diesem Fall die Zuweisung normalerweise mit Copy&Swap implementiert). Dies ist fast mit Sicherheit der Fall, denn sonst wäre nicht betont worden, dass unnötige Konstruktor und Destruktoraufrufe vermieden werden sollten. Was einen nicht-trivialen Destruktor und damit auch eine nicht-triviale Zuweisung impliziert.

    Jodocus schrieb:

    SeppJ schrieb:

    Ich bin sehr verwirrt: Wer bist du und was hast du mit dem echten Arcoth gemacht?

    Was die Pubertät so mit den Menschen macht... *duckundweg*

    Ich bin ziemlich sicher, dass Arcoth Anfang bis Mitte 20 ist.



  • SeppJ schrieb:

    Jodocus schrieb:

    SeppJ schrieb:

    Arcoth schrieb:

    manni66 schrieb:

    Arcoth schrieb:

    In meiner Demonstration habe ich einfach flüchtig ein Array statt einem vector genommen, was einen Default-Konstruktor benötig.

    Und mit einem vector würde es ohne Defaultkonstruktor funktionieren?

    😕

    std::vector<Example> vec(0, 10);
    std::iota(std::begin(e), std::end(e), 0);
    

    😕
    Sprich: Nein, es würde nicht funktionieren? Ob du nun implizit ein Array mit 0 initialisierst oder einen Vector explizit mit 0, macht doch dein Beispiel nicht besser. In jedem Fall legst du unnötige, und ausdrücklich nicht gewünschte Defaultobjekte an, bevor du die korrekten Objekte erzeugst.

    Ich stimme prinzipiell zu, allerdings hat der OP klargemacht, dass er solche Objekte wegen ihres Destruktors vermeiden möchte - ein Destruktor wird hier aber so wie ich das sehe nirgends aufgerufen, wodurch Arcoth's Lösung technisch gesehen schon den Anforderungen genügen würde.

    Wenn der Konstruktor tatsächlich Ressourcen belegt, was der Beschreibung zufolge anzunehmen ist, dann muss eine Zuweisung die überschriebene Ressource auch wieder freigeben (weshalb man in diesem Fall die Zuweisung normalerweise mit Copy&Swap implementiert). Dies ist fast mit Sicherheit der Fall, denn sonst wäre nicht betont worden, dass unnötige Konstruktor und Destruktoraufrufe vermieden werden sollten. Was einen nicht-trivialen Destruktor und damit auch eine nicht-triviale Zuweisung impliziert.

    Klar, so wird es wahrscheinlich auch sein. Aber es ist trotzdem erst mal nur Spekulation, bis der Threaderöffner Klarheit schafft.

    SeppJ schrieb:

    Jodocus schrieb:

    SeppJ schrieb:

    Ich bin sehr verwirrt: Wer bist du und was hast du mit dem echten Arcoth gemacht?

    Was die Pubertät so mit den Menschen macht... *duckundweg*

    Ich bin ziemlich sicher, dass Arcoth Anfang bis Mitte 20 ist.

    Okay, das war etwas fies. Ich denke, das ist einfach nur ein Fall von "wenn man einen Hammer hat, sieht die Welt aus wie ein Nagel". std::iota sah zu verlockend nach dem aus, was der TE vermutlich gebrauchen könnte.



  • SeppJ schrieb:

    Arcoth schrieb:

    manni66 schrieb:

    Arcoth schrieb:

    In meiner Demonstration habe ich einfach flüchtig ein Array statt einem vector genommen, was einen Default-Konstruktor benötig.

    Und mit einem vector würde es ohne Defaultkonstruktor funktionieren?

    😕

    std::vector<Example> vec(0, 10);
    std::iota(std::begin(e), std::end(e), 0);
    

    😕
    Sprich: Nein, es würde nicht funktionieren? Ob du nun implizit ein Array mit 0 initialisierst oder einen Vector explizit mit 0, macht doch dein Beispiel nicht besser. In jedem Fall legst du unnötige, und ausdrücklich nicht gewünschte Defaultobjekte an, bevor du die korrekten Objekte erzeugst.

    Zusätzlich zu den default objekten werden in jedem iota call ein temporary object erstellt:
    https://ideone.com/W7lAzJ
    Was noch weniger gewünscht ist vom OP


  • Mod

    Seppl schrieb:

    Arcoth schrieb:

    manni66 schrieb:

    Arcoth schrieb:

    In meiner Demonstration habe ich einfach flüchtig ein Array statt einem vector genommen, was einen Default-Konstruktor benötig.

    Und mit einem vector würde es ohne Defaultkonstruktor funktionieren?

    😕

    std::vector<Example> vec(0, 10);
    std::iota(std::begin(e), std::end(e), 0);
    

    😕
    Sprich: Nein, es würde nicht funktionieren? Ob du nun implizit ein Array mit 0 initialisierst oder einen Vector explizit mit 0, macht doch dein Beispiel nicht besser. In jedem Fall legst du unnötige, und ausdrücklich nicht gewünschte Defaultobjekte an, bevor du die korrekten Objekte erzeugst.

    Natürlich ist der Code furchtbar suboptimal. Was sowohl daran liegt, dass die Klasse ein RAII-konformes Interface hat, als auch dass iota_n in der STL fehlt. Dann machen wir es eben richtig:

    std::vector<Example> e;
    boost::algorithm::iota_n(back_inserter(e), 10, 0);
    
    // Oder (nicht ganz ernst gemeint)
    std::vector<Example> e;
    std::generate_n(back_inserter(e), 10,  [i = 0] () mutable {return Example(i++);});
    
    // Oder
    auto r = boost::irange(0, 9);
    std::vector<Example> e(r.begin(), r.end());
    

    Und in Bezug auf

    hustbaer schrieb:

    Die einfachste Lösung wäre wohl einen std::vector<std::unique_ptr<Foo>> statt eines std::vector<Foo> zu verwenden. Damit kontrollierst du explizit wann ein Objekt erzeugt bzw. zerstört wird, der std::vector funkt dir in keiner Weise dazwischen.

    // Oder
    std::vector<std::optional<Example>> e(10);
    std::iota(begin(e), end(e), 0);
    

    Der Punkt ist: man kann hier definitiv einen Nagel reinhauen, man muss nur hart genug schlagen! Auch wenn ich konzediere, dass mein ursprüngliches Beispiel nicht-trivial verändert werden muss, um es Sinn ergeben zu lassen. Dafür muss ich mich entschuldigen, ich habe das nicht mal ansatzweise durchdacht.

    @firefly: Diese Temporaries werden alle gemoved, vorausgesetzt der TE hat die Move-Semantik seiner Klasse richtig implementiert. Somit dürfte jeder entsprechende Destruktoraufruf ein no-op sein, da die Temporary keine Ressource mehr besitzt. Aber wie bereits erwähnt, das ist nur Spekulation.



  • Arcoth schrieb:

    Was sowohl daran liegt, dass die Klasse ein RAII-konformes Interface hat, als auch dass iota_n in der STL fehlt. Dann machen wir es eben richtig:

    std::vector<Example> e;
    boost::algorithm::iota_n(back_inserter(e), 10, 0);
    

    Wenn die klasse kein saubere implementierung von copy und/oder move semantic hat wäre hier noch ein

    e.reserve(10)
    

    von Vorteil um beim automatischen resize des vectors ein copy/move zu vermeiden.
    Falls boost nicht verwendet werden kann/soll kann man das gleiche auch mit folgende code erreichen:

    std::vector<Example> e;
    e.reserve(10);
    for (int i = 0; i < 10; ++i)
    {
      e.emplace_back(i);
    }
    

    Wobei der code c++11 features benötigt.


  • Mod

    firefly schrieb:

    Arcoth schrieb:

    Was sowohl daran liegt, dass die Klasse ein RAII-konformes Interface hat, als auch dass iota_n in der STL fehlt. Dann machen wir es eben richtig:

    std::vector<Example> e;
    boost::algorithm::iota_n(back_inserter(e), 10, 0);
    

    Wenn die klasse kein saubere implementierung von copy und/oder move semantic hat wäre hier noch ein

    e.reserve(10)
    

    von Vorteil um beim automatischen resize des vectors ein copy/move zu vermeiden.

    Wenn die Klasse keine saubere Move-Semantik hat, es aber bedeutend ist, ihre Ressourcen nicht zu duplizieren, hat der TE etwas anderes aufzuholen. Ersteres ist doch gerade aus dem ubiquitaeren Wunsch geboren worden, Ressourcen nicht verschwenderisch kopieren zu muessen, wenn man die besitzenden Objekte verschieben muss. Ich meine, sogar in C++03 koennte man die Boost.Move Loesung einsetzen.



  • Ich habe da noch ein Problem. Im Moment sieht es so bei mir aus:

    std::vector<Example> examples;
      controllers.reserve(100);
      for(int i = 0; i < 100; i++)
      {
        examples.emplace_back(std::move(Example(i)));
    
        examples[i].foo();
    
      }
    

    Es wird dennoch für jedes Example ein Destruktor aufgerufen, bevor das Ende der Gültigkeit des vectors erreicht wird.
    Anschließend gibt es noch einen zweiten Destruktor. Ich gehe also davon aus, dass irgendwo kopiert wird, was ich vermeiden möchte.

    Ich lasse mir auch gerne erklären, was da genau passiert, wenn sich jemand die Zeit dafür nehmen möchte.


  • Mod

    emplace_back(i)?

    Auch wenn du den temporären Wert movest, so existiert er doch als Objekt und muss irgendwann zerstört werden. Wenn du die move-Semantik korrekt implementiert hast, dann sollte dabei aber keine Ressourcenfreigabe nötig sein, schließlich wurden die Ressourcen woanders hin gemoved und nicht kopiert. Anscheinend ist das aber nicht der Fall, sonst würdest du nicht fragen. Oder fragst du, weil du nur die Anzahl der Destruktoraufrufe gezählt hast, ohne der Spur der Ressourcen zu folgen?

    Mit emplace_back(i) sollte sich das aber auf jeden Fall erledigen, egal ob du berechtigt oder unberechtigt fragst. Dazu ist emplace schließlich da. emplace und move gleichzeitig sind hingegen eine komische Kombination.



  • (removed, habe dasselbe wie SeppJ geschrieben)



  • Ok, habe emplace_back falsch angewandt und stumpf Destruktor-Aufrufe gezählt.


  • Mod

    Das ist übrigens auch der Grund, warum back_inserter einen Haufen Temporaries erzeugt; es wird push_back statt emplace_back eingesetzt, was implizit eine Temporary mit 0 initialisiert und von dieser dann moved. Wir bräuchten eigentlich back_emplacer ... müsste sich aber relativ leicht mit Boost.Iterator bauen lassen.


Anmelden zum Antworten