Jak mogę przekazać std :: unique_ptr do funkcji

101

Jak mogę przekazać a std::unique_ptrdo funkcji? Powiedzmy, że mam następującą klasę:

class A
{
public:
    A(int val)
    {
        _val = val;
    }

    int GetVal() { return _val; }
private:
    int _val;
};

Następujące elementy nie są kompilowane:

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);

    return 0;
}

Dlaczego nie mogę przekazać a std::unique_ptrdo funkcji? Z pewnością to jest główny cel konstrukcji? Czy też komisja C ++ miała zamiar wrócić do surowych wskaźników w stylu C i przekazać to w ten sposób:

MyFunc(&(*ptr)); 

A co najdziwniejsze, dlaczego jest to dobry sposób na to, by go ominąć? Wydaje się okropnie niekonsekwentne:

MyFunc(unique_ptr<A>(new A(1234)));
user3690202
źródło
7
Nie ma nic złego w „cofaniu się” do surowych wskaźników w stylu C, o ile nie są one właścicielami surowych wskaźników. Może wolisz napisać „ptr.get ()”. Chociaż jeśli nie potrzebujesz wartości null, preferowane byłoby odwołanie.
Chris Drew

Odpowiedzi:

149

Zasadniczo są tutaj dwie opcje:

Przekaż inteligentny wskaźnik przez odniesienie

void MyFunc(unique_ptr<A> & arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);
}

Przenieś inteligentny wskaźnik do argumentu funkcji

Zauważ, że w tym przypadku asercja zostanie zachowana!

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(move(ptr));
    assert(ptr == nullptr)
}
Bill Lynch
źródło
@VermillionAzure: Funkcja, do której się odnosisz, jest zwykle nazywana niemożliwą do skopiowania, a nie unikalną.
Bill Lynch
1
Dzięki. W tym przypadku chciałem utworzyć instancję, wykonać na niej kilka czynności, a następnie przekazać własność komuś innemu, dla czego move () wydaje się być idealny.
user3690202
7
Powinieneś przekazywać unique_ptrprzez referencję tylko wtedy, gdy funkcja może lub nie może się z niej ruszyć. A potem powinno to być odniesienie do wartości r. Aby obserwować obiekt bez wymagania czegokolwiek na temat jego semantyki własności, użyj odniesienia takiego jak A const&lub A&.
Potatoswatter
14
Posłuchaj wykładu Herba Suttersa na CppCon 2014. Zdecydowanie odradza przekazywanie odniesień do unique_ptr <>. Istotą jest to, że kiedy masz do czynienia z własnością, powinieneś po prostu używać inteligentnych wskaźników, takich jak unique_ptr <> lub shared_ptr <>. Zobacz www.youtube.com/watch?v=xnqTKD8uD64 Tutaj można znaleźć slajdy github.com/CppCon/CppCon2014/tree/master/Presentations/...
schorsch_76
2
@Furihr: To tylko sposób na pokazanie wartości ptrpo przeprowadzce.
Bill Lynch
28

Przekazujesz to według wartości, co oznacza wykonanie kopii. To nie byłoby wyjątkowe, prawda?

Możesz przenieść wartość, ale oznacza to przekazanie własności obiektu i kontrolę jego życia do funkcji.

Jeśli czas życia obiektu jest gwarantowany przez cały czas trwania wywołania MyFunc, po prostu przekaż nieprzetworzony wskaźnik via ptr.get().

Mark Tolonen
źródło
18

Dlaczego nie mogę przekazać a unique_ptrdo funkcji?

Nie możesz tego zrobić, ponieważ unique_ptrma konstruktor przenoszenia, ale nie ma konstruktora kopiującego. Zgodnie ze standardem, jeśli zdefiniowano konstruktor przenoszenia, ale nie zdefiniowano konstruktora kopiującego, konstruktor kopiujący jest usuwany.

12.8 Kopiowanie i przenoszenie obiektów klas

...

7 Jeśli definicja klasy nie deklaruje jawnie konstruktora kopiującego, jest on deklarowany niejawnie. Jeśli definicja klasy deklaruje konstruktor przenoszenia lub operator przypisania przenoszenia, niejawnie zadeklarowany konstruktor kopiujący jest zdefiniowany jako usunięty;

Możesz przekazać unique_ptrdo funkcji przy pomocy:

void MyFunc(std::unique_ptr<A>& arg)
{
    cout << arg->GetVal() << endl;
}

i używaj go tak, jak masz:

lub

void MyFunc(std::unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

i używaj go jak:

std::unique_ptr<A> ptr = std::unique_ptr<A>(new A(1234));
MyFunc(std::move(ptr));

Ważna uwaga

Zauważ, że jeśli używasz drugiej metody, ptrnie ma prawa własności do wskaźnika po wywołaniu std::move(ptr)return.

void MyFunc(std::unique_ptr<A>&& arg) miałby taki sam efekt jak void MyFunc(std::unique_ptr<A>& arg) ponieważ oba są odniesieniami.

W pierwszym przypadku ptrnadal ma własność wskaźnika po wywołaniu MyFunc.

R Sahu
źródło
6

Ponieważ MyFuncnie przejmuje na własność, lepiej byłoby mieć:

void MyFunc(const A* arg)
{
    assert(arg != nullptr); // or throw ?
    cout << arg->GetVal() << endl;
}

albo lepiej

void MyFunc(const A& arg)
{
    cout << arg.GetVal() << endl;
}

Jeśli naprawdę chcesz przejąć na własność, musisz przenieść swój zasób:

std::unique_ptr<A> ptr = std::make_unique<A>(1234);
MyFunc(std::move(ptr));

lub przekaż bezpośrednio odniesienie do wartości r:

MyFunc(std::make_unique<A>(1234));

std::unique_ptr nie ma celowo kopii gwarantującej posiadanie tylko jednego właściciela.

Jarod42
źródło
W tej odpowiedzi brakuje tego, jak wygląda wywołanie MyFunc w pierwszych dwóch przypadkach. Nie masz pewności, czy używasz, ptr.get()czy tylko przekazujesz ptrdo funkcji?
taylorstine
Jaki jest zamierzony podpis MyFuncdla przekazania odniesienia wartości r?
James Hirschorn
@JamesHirschorn: void MyFunc(A&& arg)bierze odniesienie do wartości r ...
Jarod42
@ Jarod42 Zgadłem, ale z typedef int A[], MyFunc(std::make_unique<A>(N))daje błąd kompilatora: błąd: niepoprawna inicjalizacja odniesienia typu „int (&&) []” z wyrażenia typu „std :: _ MakeUniq <int []> :: __ array” { aka 'std :: unique_ptr <int [], std :: default_delete <int []>>'} Czy jest g++ -std=gnu++11wystarczająco aktualny?
James Hirschorn
@ Jarod42 Potwierdziłem awarię C ++ 14 z ideone: ideone.com/YRRT24
James Hirschorn
4

Dlaczego nie mogę przekazać a unique_ptrdo funkcji?

Możesz, ale nie przez kopiowanie - ponieważ std::unique_ptr<>nie można go kopiować.

Z pewnością to jest główny cel konstrukcji?

Między innymi std::unique_ptr<>ma na celu jednoznaczne oznaczenie unikalnej własności (w przeciwieństwie do std::shared_ptr<>).

A co najdziwniejsze, dlaczego jest to dobry sposób na to, by go ominąć?

Ponieważ w tym przypadku nie ma konstrukcji kopiowania.

Nielk
źródło
Dzięki za odpowiedź. Tak tylko z ciekawości, czy możesz wyjaśnić, dlaczego ostatnim sposobem przekazania tego nie jest użycie konstruktora kopiującego? Pomyślałbym, że użycie konstruktora unique_ptr spowoduje wygenerowanie wystąpienia na stosie, które zostanie skopiowane do argumentów MyFunc () przy użyciu konstruktora kopiującego? Chociaż przyznaję, że moje wspomnienia w tej dziedzinie są trochę niejasne.
user3690202
2
Ponieważ jest to wartość r, zamiast niej wywoływany jest konstruktor przenoszenia. Chociaż twój kompilator z pewnością i tak to zoptymalizuje.
Nielk
0

Ponieważ unique_ptrsłuży do unikalnej własności, jeśli chcesz przekazać to jako argument, spróbuj

MyFunc(move(ptr));

Ale po tym, że stan ptrIN mainbędzie nullptr.

0x6773
źródło
4
„będzie niezdefiniowane” - nie, będzie zerowe lub unique_ptrraczej bezużyteczne.
TC
0

Przekazywanie funkcji std::unique_ptr<T>jako wartość nie działa, ponieważ, jak wspomnieliście, unique_ptrnie można jej skopiować.

A co z tym?

std::unique_ptr<T> getSomething()
{
   auto ptr = std::make_unique<T>();
   return ptr;
}

ten kod działa

Riste
źródło
Jeśli ten kod zadziała, przypuszczam, że nie kopiuje on unique_ptr, ale w rzeczywistości używa semantyki przenoszenia, aby go przenieść.
user3690202
może być optymalizacja wartości zwrotu (RVO), jak sądzę
Noueman Khalikine