Keresés

Új hozzászólás Aktív témák

  • dobragab

    addikt

    válasz EQMontoya #3502 üzenetére

    Csak elkészült ez a válasz, túl régóta írom. :D

    Az emplace_back veszélyét szerintem az első világítja meg legjobban. Nem te hívod meg a konstruktort, így történhetnek olyan konverziók, amit nem szeretnénk.

    A második példában az igazi körmös nem az emplace_back, hanem a RAII megsértéséért jár. Sőt, rosszabb: RAII és raw pointer keverése. Ugye resource acquisition is initialization lenne, csakhogy a foglalás után pakolod bele egy unique_ptr-be, itt épp implicit módon.

    Ha azt vesszük, hogy a vector deklarációja zömmel három forrásfájllal és 2000 sorral arrébb van, mint ahol belepakolsz, és lehet, hogy rosszul emlékszel, hogy az most A*-ot, vagy unique_ptr<A>-t tartalmaz. push_back-nél ha rosszul emlékszel, a fordító hörögve kiszáll.

    Szóval a második esetért közvetve az emplace_back felelős, közvetlenül az, aki nem emlékszik, hogy ott unique_ptr van. Általános esetben is, az emplace_back-nél nem mindig kell tudnod, hogy temporális vagy létező cuccot akarsz-e belerakni, és ez itt a fő veszélyforrás.

    Engem kísértetiesen emlékeztet a kérdés a C-s NULL és a 0 esetére, a hatékonyságot kivéve. Mindenhova írhatsz 0-t, ahova NULL-t, de fordítva nem igaz. Most ne keverjük ide a printf-et, talán az az egy kivétel van. Mégis, az olvashatóság kedvéért pointereknél NULL-t, egészeknél 0-t írunk, pedig írhatnánk mindenhova 0-t. Ugyanígy, az olvashatóság kedvéért létező objektumnál tessék push_back-et írni, hogy biztosak lehessünk benne, ott tényleg létező objektumról van szó.

    Ennyi indok bőven elég, hogy push_back helyett ne használjunk emplace_back-et.
    Továbbmegyek: van egyáltalán létjogosultsága az emplace_back-nek?

    Ha egy már létező objektumot akarsz belepakolni, a kettő pont ugyanannyira hatékony, ugyanaz történik. Megnéztem, mi a helyzet abban az esetben is, amire az emplace_back elsődlegesen való: temporális objektumnál. Igazából arra ment ki a játék, hogy ilyenkor jobb-e az emplace_back, mint a push_back kiírt konstruktorhívással. Azért mégis csak jobban látni, hogy ott egy temporális objektumot pakolunk bele a vektorba, ha oda van írva.

    Ilyenkor szoktam elővenni a Noisy osztályt, ami mindig kiírja, mikor mi történik vele, és számolja, hány példány van belőle, amúgy semmi másra nem jó. Itt a teljes forráskód, a Noisy túl hosszú ahhoz, hogy gátlástalanul bemásoljam, ide csak a lényeget hoztam.

    int main()
    {
    std::vector<Noisy> vec;
    vec.reserve (1000);
    Noisy n1(1);
    vec.emplace_back(n1);
    Noisy n2(2);
    vec.push_back(n2);

    vec.emplace_back(Noisy(3));
    vec.push_back (Noisy(4));
    vec.emplace_back(5);
    }

    Az elején azért van ott a ronda reserve, hogy ne legyen átméreteződés, az csak összezavar minket.

    Az n1 és az n2 esetében kottára ugyanaz történik, ahogy el is várjuk, copy ctor. Noisy(3) és Noisy(4) is egyforma, move ctor. Az igazán érdekes a 4-es és 5-ös összehasonlítása. Annyit nyertünk az 5-össel a 4-eshez képest, hogy egy move ctorral és egy ki-move-olt objektum destruktorával kevesebb. Ezen spórolni meg igencsak barbár dolog C++11 óta.

    Ha kikommenteled a move ctort, akkor a fordító sem generál - mert írtunk destruktort -, így a copy ctor hívódik, tehát a push_back lényegesen lassabb lesz.

    És mivel az emplace_back eleve C++11, így jó eséllyel van move ctora a tárolt cuccnak. Akkor van igazán értelme az emplace_back-nek, ha nincs move ctor, ilyen-olyan okból. Nem is lehet neki jó move ctort írni - ilyet nehezen tudok elképzelni, de C++-ban semmit nem zárok ki -, vagy C++98-as, nem módosítható API-ból jön.

    Ha nincs move ctor, a hatékonyság számít annyit, hogy szerintem elfogadható az emplace_back. Egyéb esetben én azt mondom, fordítva kéne: emplace_back helyett is push_back-et használni: fontosabb az, hogy lásd kiírva a ctort, mint a move ctor overhead-je.

    Akkor viszont egy másik probléma jön elő: ha van move ctor, akkor push_back, ha nincs, emplace_back. Ez kb. ugyanolyan rossz, mint az eredeti probléma, szóval temporális objektumra egységesen kéne a kettő közül választani. Nézzük meg a várható kockázatokat és a nyereséget:

    push_back: ha nincs move ctor, lassú, de jól olvasható és egyértelmű
    emplace_back: ha nincs move ctor, gyorsabb, néhány karakterrel rövidebb. Várható kockázat: egy elrontott emplace_back miatt végtelen debuggolás...

    Szóval én azt mondom, temporális objektumnál az API függvényében kéne dönteni, hogy az adott projektben melyik. De létező objektumra egész biztosan push_back.

Új hozzászólás Aktív témák