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)));

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)
}

@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().


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.


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.


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.


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.


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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.