przekazywanie unique_ptr do funkcji przez r-value czy l-value reference?

0

Hej
Który kod poniżej jest lepszy (z użyciem r-value reference czy l-value reference)? Czy to obojętne? Wynk końcowy jest taki sam. To przykład z pracy, metoda cośtam robi, czasem może przejąć ownership nad pointerem a czasem nie.

#include <stdio.h>
#include <string>
#include <iostream>
#include <memory>

void metoda(std::unique_ptr<std::string>&& ptr)
{
   std::unique_ptr<std::string> ptr2 = std::move(ptr);
}

int main()
{
    std::unique_ptr<std::string> ptr(new std::string("dummy"));
    
    metoda(std::move(ptr));
    
    if(ptr)
        std::cout << "ptr TRUE" << std::endl;
    
    return 0;
}

czy

#include <stdio.h>
#include <string>
#include <iostream>
#include <memory>

void metoda(std::unique_ptr<std::string>& ptr)
{
   std::unique_ptr<std::string> ptr2 = std::move(ptr); 
}

int main()
{
    std::unique_ptr<std::string> ptr(new std::string("dummy"));
    
    metoda(ptr);
    
    if(ptr)
        std::cout << "ptr TRUE" << std::endl;
    
    return 0;
}
1

Jeśli chodzi o poprawność semantyczną to bez różnicy, ale jak widzę move, to spodziewam się, że na pewno zostanie wykonane move.

Więc wersję z move wolałbym taką:

int main()
{
    std::unique_ptr<std::string> ptr(new std::string("dummy"));

    ptr = metoda(std::move(ptr));

    if(ptr)
        std::cout << "ptr TRUE" << std::endl;

    return 0;
}
1

unique_ptr jak nazwa wskazuje jest unikalnym wskaźnikiem na obiekt. Zawsze jest tylko jeden wskaźnik na instancję obiektu.
Jeśli nie ma transferu własności to przekazujesz wskaźnik surowy, albo referencję na obiekt.
Jak jest masz transfer własności to, move reference jest obowiązkowa.

Twój przykład jest tak nieżyciowy, że nie da sie powiedzieć, czy jest dobrze czy źle.
Zawsze jak widzę new std::string to jest dla mnie sygnałem, że ktoś dopiero się uczy, albo ktoś nie wie co robi.

Polecam ten filmik.

0

zapomniałem o make_unique i stąd new, bo z 5 miesięcy nie używałem unique_ptra.:)
Mój przykład jest własnie z pracy. Użytkownik rejestruje callbacki w mapie, w której kluczem jest identyfikator mesydża a value to własnie callback.
No i nie wiem jaki dać wzór callbacka z l-value czy r-value:

using MsgCallback = std::function<void(unique_ptr<struktura opisująca mesydż>&&, int source)>;

Mesydż już by default jest w unique_pointerze, na to nie mam wpływu, w pracy po prostu silnik takie coś mi zwraca. Kwestia czy przekazac to przez r-value czy l-value. Musze interfejs zapewnić w dla tego callbacka.
Po wywołaniu callbacka i tak na wszelki wypadek w silniku wywołuję metodę reset() w razie przysłowiowego Niemca:)

it->second(std::move(msg), source);
msg.reset(); //just in case user forgot to deallocate memory in his own callback handler

Niemniej jednak może dac r-value bo jak użytkownik zobaczy r-value to będzie wiedział, że to jego zadanie by zwolnic pamięć w callbacku.

3
fvg napisał(a):

zapomniałem o make_unique i stąd new

Nie o to chodzi. std::string już sam zarządza pamięcią na stercie. Tworzenie go bazpośrednio na stercie za pomocą new lub unique_ptr nie ma sensu.
Mało tego, większość kompilatorów implementuje, Small String Optimization (SSO), które polega na tym, że małe napisy trzymane są bez udziału sterty.
Używając new cała optymalizacja SSO traci sens.

0

no to był tylko taki dummy przykład z tym stringiem, w rzeczywistości w pracy tam jest duża struktura opisująca mesydż z różnorakimi typami pól.

0

Odpowiedz sobie na ważne pytanie, czy przekazujesz ownership? Jak tak to &&, ja sam głównie robię unique_ptr przez &&.

Dyskusja
https://stackoverflow.com/questions/8114276/how-do-i-pass-a-unique-ptr-argument-to-a-constructor-or-a-function/8114913

0

it->second(std::move(msg), source); msg.reset(); //just in case user forgot to deallocate memory in his own callback handler

albo ja jestem głupi i nie wiem jak to działa albo ten reset jest totalnie bezsensowny, jeżeli nawet w callbacku ktoś tego unique_ptra gdzieś na zewnątrz wyrzuci to msg już tą pamięcią nie będzie zarządzać, czyli masz no-opa.

0
fvg napisał(a):

zapomniałem o make_unique i stąd new, bo z 5 miesięcy nie używałem unique_ptra.:)
Mój przykład jest własnie z pracy. Użytkownik rejestruje callbacki w mapie, w której kluczem jest identyfikator mesydża a value to własnie callback.
No i nie wiem jaki dać wzór callbacka z l-value czy r-value:

using MsgCallback = std::function<void(unique_ptr<struktura opisująca mesydż>&&, int source)>;

Mesydż już by default jest w unique_pointerze, na to nie mam wpływu, w pracy po prostu silnik takie coś mi zwraca. Kwestia czy przekazac to przez r-value czy l-value. Musze interfejs zapewnić w dla tego callbacka.
Po wywołaniu callbacka i tak na wszelki wypadek w silniku wywołuję metodę reset() w razie przysłowiowego Niemca:)

it->second(std::move(msg), source);
msg.reset(); //just in case user forgot to deallocate memory in his own callback handler

Niemniej jednak może dac r-value bo jak użytkownik zobaczy r-value to będzie wiedział, że to jego zadanie by zwolnic pamięć w callbacku.

przekazałeś gdzieś sobie owner ship, uniqe_ptr o nazwie msg już nie jest właścicielem zasobu. Po kiego grzyba wołasz reset? Co ty chcesz resetować? Po tym kodzie widzę że tam chłopaki musicie poczytać efficient modern c++ w pracy.

0
revcorey napisał(a):

przekazałeś gdzieś sobie owner ship, uniqe_ptr o nazwie msg już nie jest właścicielem zasobu. Po kiego grzyba wołasz reset? Co ty chcesz resetować? Po tym kodzie widzę że tam chłopaki musicie poczytać efficient modern c++ w pracy.

Ja zepewniam interfejs, metodę (callbacka) userzy będą definiować i rejestrować je. Co jak ktoś nie zwolni przekazanego przez && unique_ptra? Wtedy w moim kontekscie on dalej będzie True jak w przypadku poniżej:

#include <stdio.h>
#include <string>
#include <iostream>
#include <memory>

struct Klasa
{
    public:
    Klasa()=default;
    Klasa(const Klasa& kl)
    {
        napis = kl.napis;
        std::cout << "copy ctor" << std::endl;
    }
    Klasa(Klasa&& kl)
    {
        napis = std::move(kl.napis);
        std::cout << "move ctor" << std::endl;
    }
    std::string napis;
};

void metoda(std::unique_ptr<Klasa>&& ptr)
{
    ptr->napis = "sadsasa";
}

int main()
{
    std::unique_ptr<Klasa> ptr(new Klasa);
    ptr->napis = "dddd";
    
    metoda(std::move(ptr));
    
    if(ptr)
    {
        std::cout << "ptr TRUE" << std::endl;
    }
    
    return 0;
}


Aczkolwiek zgadzam się że jakbym nie tworzył interfejsu, czyli to ja tylko definiował metodę to wtedy ten reset nie byłby potrzebny. Ale to ze sto ludzi z różnym doświadczeniem w kontekscie swoich procesów,apek będzie definiowac metodę callbackową i stąd musze być odporny na to że ktoś nie przejmie ownershipu.
Ale chyba dam przekazywanie jako l-value reference. Może taka powinna być praktyka programowania że w callbackach nigdy nie dawać argumentu jako r-value reference bo nie masz gwarancji że user Ci zwolni pamięć, a przy l-value reference masz pewność że to Ty w silniku musisz zadbać o zwolnienie pamięci.

0

No i to działa prawidłowo. A teraz przekaż unique_ptr przez kopię z std::move i zobacz co się stanie.
Przez rvalue ref zadziała dobrze o ile skonsumujesz referencję w środku funkcji metoda, tzn. dokądś ją przypiszesz (przy czym bez jej przypisywania zadziała to jak zwykła referencja. I to jest dopiero pułapka :P)

0

No własnie i na tą pułapkę muszę być odporny.

1

A możesz przekazać unique_ptra przez wartość? Wtedy jesteś zawsze odporny.

0

unique_ptr copy ctora nie ma także tylko l-value referencja bądź r.
Jeszcze shared_ptra można by użyć, zamienić unique'a. Ale dobra zostaje l-value ref.

1

Nieprawda. Przekazujesz przez value i robisz w wywołaniu funkcji std::move

1 użytkowników online, w tym zalogowanych: 0, gości: 1