Bagaimana cara mengirimkan std :: unique_ptr ke dalam suatu fungsi

97

Bagaimana cara mengirimkan a std::unique_ptrke suatu fungsi? Katakanlah saya memiliki kelas berikut:

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

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

Berikut ini tidak dapat dikompilasi:

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

Mengapa saya tidak dapat meneruskan a std::unique_ptrke suatu fungsi? Tentunya ini tujuan utama dari konstruksinya? Atau apakah komite C ++ bermaksud agar saya kembali ke petunjuk gaya C mentah dan meneruskannya seperti ini:

MyFunc(&(*ptr)); 

Dan yang paling aneh dari semuanya, mengapa ini cara yang OK untuk melewatinya? Tampaknya sangat tidak konsisten:

MyFunc(unique_ptr<A>(new A(1234)));
pengguna3690202
sumber
7
Tidak ada yang salah dengan "mundur" ke petunjuk gaya-C yang mentah selama mereka tidak memiliki petunjuk mentah. Anda mungkin lebih suka menulis 'ptr.get ()'. Meskipun, jika Anda tidak membutuhkan nullability, referensi akan lebih disukai.
Chris Drew

Jawaban:

142

Pada dasarnya ada dua opsi di sini:

Meneruskan penunjuk cerdas dengan referensi

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

Pindahkan penunjuk cerdas ke dalam argumen fungsi

Perhatikan bahwa dalam kasus ini, pernyataan akan berlaku!

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
sumber
@VermillionAzure: Fitur yang Anda maksud biasanya disebut tidak dapat disalin, tidak unik.
Bill Lynch
1
Terima kasih. Dalam hal ini saya ingin membuat sebuah instance, melakukan beberapa tindakan padanya dan kemudian menyerahkan kepemilikan kepada orang lain, yang tampaknya cocok untuk move ().
pengguna3690202
7
Anda hanya boleh meneruskan unique_ptrby reference jika fungsinya mungkin atau mungkin tidak berpindah darinya. Dan kemudian itu harus menjadi referensi nilai r. Untuk mengamati suatu objek tanpa memerlukan apa pun tentang semantik kepemilikannya, gunakan referensi seperti A const&atau A&.
Potatoswatter
12
Dengarkan pembicaraan Herb Sutters di CppCon 2014. Dia sangat tidak menyarankan, untuk meneruskan referensi ke unique_ptr <>. Intinya adalah, Anda sebaiknya menggunakan petunjuk cerdas seperti unique_ptr <> atau shared_ptr <> saat Anda berurusan dengan kepemilikan. Lihat www.youtube.com/watch?v=xnqTKD8uD64 Di sini Anda dapat menemukan slide github.com/CppCon/CppCon2014/tree/master/Presentations/…
schorsch_76
2
@Furihr: Ini hanya cara untuk menunjukkan nilai ptrsetelah pindah.
Bill Lynch
26

Anda meneruskannya dengan nilai, yang berarti membuat salinan. Itu tidak terlalu unik, bukan?

Anda bisa memindahkan nilainya, tetapi itu berarti meneruskan kepemilikan objek dan kontrol masa pakainya ke fungsi.

Jika masa pakai objek dijamin ada selama masa panggilan ke MyFunc, teruskan saja pointer mentah melalui ptr.get().

Mark Tolonen
sumber
17

Mengapa saya tidak dapat meneruskan a unique_ptrke suatu fungsi?

Anda tidak dapat melakukannya karena unique_ptrmemiliki konstruktor pemindahan tetapi bukan konstruktor salinan. Menurut standar, ketika konstruktor pemindahan ditentukan tetapi konstruktor salinan tidak ditentukan, konstruktor salinan dihapus.

12.8 Menyalin dan memindahkan objek kelas

...

7 Jika definisi kelas tidak secara eksplisit mendeklarasikan salinan konstruktor, satu akan dideklarasikan secara implisit. Jika definisi kelas mendeklarasikan konstruktor pemindahan atau operator penugasan pindah, konstruktor salinan yang dideklarasikan secara implisit didefinisikan sebagai dihapus;

Anda dapat meneruskan unique_ptrke fungsi dengan menggunakan:

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

dan gunakan seperti yang Anda miliki:

atau

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

dan gunakan seperti:

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

Catatan penting

Harap dicatat bahwa jika Anda menggunakan metode kedua, ptrtidak memiliki kepemilikan pointer setelah panggilan untuk std::move(ptr)kembali.

void MyFunc(std::unique_ptr<A>&& arg)akan memiliki efek yang sama void MyFunc(std::unique_ptr<A>& arg)karena keduanya adalah referensi.

Dalam kasus pertama, ptrmasih memiliki kepemilikan penunjuk setelah panggilan ke MyFunc.

R Sahu
sumber
5

Karena MyFunctidak mengambil kepemilikan, akan lebih baik untuk memiliki:

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

atau lebih baik

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

Jika Anda benar-benar ingin mengambil alih kepemilikan, Anda harus memindahkan sumber daya Anda:

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

atau berikan referensi nilai-r secara langsung:

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

std::unique_ptr tidak memiliki salinan dengan tujuan untuk menjamin hanya memiliki satu pemilik.

Jarod42
sumber
Jawaban ini tidak memiliki tampilan panggilan ke MyFunc untuk dua kasus pertama Anda. Tidak yakin apakah Anda menggunakan ptr.get()atau hanya meneruskan ptrke fungsi?
taylorstine
Apa tanda tangan yang dimaksudkan MyFuncuntuk meneruskan referensi nilai-r?
James Hirschorn
@JamesHirschorn: void MyFunc(A&& arg)mengambil referensi nilai-r ...
Jarod42
@ Jarod42 Itulah yang saya duga, tetapi dengan typedef int A[], MyFunc(std::make_unique<A>(N))memberikan kesalahan kompiler: error: inisialisasi referensi yang tidak valid dari tipe 'int (&&) []' dari ekspresi tipe 'std :: _ MakeUniq <int []> :: __ array' { aka 'std :: unique_ptr <int [], std :: default_delete <int []>>'} Apakah g++ -std=gnu++11cukup terkini?
James Hirschorn
@ Jarod42 Saya sudah konfirmasi kegagalan untuk C ++ 14 dengan ideone: ideone.com/YRRT24
James Hirschorn
4

Mengapa saya tidak dapat meneruskan a unique_ptrke suatu fungsi?

Anda bisa, tetapi tidak dengan menyalin - karena std::unique_ptr<>tidak dapat dibuat salinan.

Tentunya ini tujuan utama dari konstruksinya?

Antara lain, std::unique_ptr<>dirancang untuk secara tegas menandai kepemilikan unik (sebagai lawan std::shared_ptr<>).

Dan yang paling aneh dari semuanya, mengapa ini cara yang OK untuk melewatinya?

Karena dalam kasus itu, tidak ada konstruksi salinan.

Nielk
sumber
Terima kasih atas jawaban anda. Hanya karena tertarik, dapatkah Anda menjelaskan mengapa cara terakhir untuk meneruskannya tidak menggunakan konstruktor salinan? Saya akan berpikir bahwa penggunaan konstruktor unique_ptr akan menghasilkan sebuah instance pada stack, yang akan disalin ke dalam argumen untuk MyFunc () menggunakan konstruktor copy? Meskipun saya akui ingatan saya agak kabur di bidang ini.
pengguna3690202
2
Karena ini adalah nilai r, konstruktor pemindah dipanggil sebagai gantinya. Meskipun kompiler Anda pasti akan mengoptimalkan ini.
Nielk
0

Karena unique_ptruntuk kepemilikan unik, jika Anda ingin menyebarkannya sebagai argumen coba

MyFunc(move(ptr));

Tetapi setelah itu keadaan ptrdalam mainakan menjadi nullptr.

0x6773
sumber
4
"tidak akan ditentukan" - tidak, itu akan menjadi nol, atau unique_ptrakan menjadi tidak berguna.
TC
0

Meneruskan std::unique_ptr<T>sebagai nilai ke suatu fungsi tidak berfungsi karena, seperti yang Anda sebutkan, unique_ptrtidak dapat disalin.

Bagaimana dengan ini?

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

kode ini berfungsi

Riste
sumber
Jika kode itu berfungsi maka tebakan saya adalah itu tidak menyalin unique_ptr, tetapi pada kenyataannya menggunakan semantik bergerak untuk memindahkannya.
pengguna3690202
Mungkin Pengoptimalan Nilai Pengembalian (RVO) kurasa
Noueman Khalikine