Haruskah saya meneruskan std :: function by const-reference?


147

Katakanlah saya memiliki fungsi yang membutuhkan std::function:

void callFunction(std::function<void()> x)
{
    x();
}

Haruskah saya melewati xreferensi const sebagai gantinya ?:

void callFunction(const std::function<void()>& x)
{
    x();
}

Apakah jawaban atas pertanyaan ini berubah tergantung pada apa fungsinya dengannya? Misalnya jika itu adalah fungsi anggota kelas atau konstruktor yang menyimpan atau menginisialisasi std::functionmenjadi variabel anggota.


1
Mungkin tidak. Saya tidak tahu pasti, tapi saya berharap sizeof(std::function)tidak lebih dari 2 * sizeof(size_t), yang merupakan ukuran terkecil yang pernah Anda pertimbangkan untuk referensi const.
Mats Petersson

12
@ Mat: Saya rasa ukuran std::functionpembungkusnya tidak sepenting kerumitan menyalinnya. Jika deep copy dilibatkan, harganya bisa jauh lebih mahal daripada yang sizeofdisarankan.
Ben Voigt

Haruskah Anda movemenjalankan fungsinya?
Yakk - Adam Nevraumont

operator()()adalah constbegitu referensi const harus bekerja. Tapi saya tidak pernah menggunakan std :: function.
Neel Basu

@Yakk Saya baru saja meneruskan lambda langsung ke fungsi.
Sven Adbring

Jawaban:


81

Jika Anda menginginkan kinerja, berikan nilai jika Anda menyimpannya.

Misalkan Anda memiliki fungsi yang disebut "jalankan ini di thread UI".

std::future<void> run_in_ui_thread( std::function<void()> )

yang menjalankan beberapa kode di utas "ui", lalu memberi sinyal futuresaat selesai. (Berguna dalam kerangka UI di mana utas UI adalah tempat Anda seharusnya mengacaukan elemen UI)

Kami memiliki dua tanda tangan yang sedang kami pertimbangkan:

std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)

Sekarang, kami cenderung menggunakan ini sebagai berikut:

run_in_ui_thread( [=]{
  // code goes here
} ).wait();

yang akan membuat penutupan anonim (lambda), membuat std::functionkeluar darinya, meneruskannya ke run_in_ui_threadfungsi, lalu menunggu hingga selesai berjalan di utas utama.

Dalam kasus (A), std::functionsecara langsung dibangun dari lambda kami, yang kemudian digunakan dalam file run_in_ui_thread. Lambda adalah moved ke dalam std::function, sehingga setiap status bergerak secara efisien dibawa ke dalamnya.

Dalam kasus kedua, sementara std::functiondibuat, lambda moved ke dalamnya, kemudian sementara std::functionitu digunakan sebagai referensi dalam file run_in_ui_thread.

Sejauh ini, sangat bagus - keduanya tampil identik. Kecuali run_in_ui_threadakan membuat salinan argumen fungsinya untuk dikirim ke utas ui untuk dieksekusi! (itu akan kembali sebelum selesai dengannya, jadi tidak bisa hanya menggunakan referensi ke sana). Untuk kasus (A), kami hanya movememasukkan std::functionke dalam penyimpanan jangka panjangnya. Dalam kasus (B), kami dipaksa untuk menyalin file std::function.

Penyimpanan itu membuat nilai lewat lebih optimal. Jika ada kemungkinan Anda menyimpan salinan dari nilai std::functionlewat. Jika tidak, cara mana pun secara kasar setara: satu-satunya sisi negatif dari nilai sampingan adalah jika Anda menggunakan ukuran besar yang sama std::functiondan memiliki satu sub metode setelah yang lain menggunakannya. Kecuali itu, a moveakan seefisien a const&.

Sekarang, ada beberapa perbedaan lain antara keduanya yang kebanyakan muncul jika kita memiliki status persisten di dalam std::function.

Asumsikan bahwa std::functionmenyimpan beberapa objek dengan a operator() const, tetapi juga memiliki beberapa mutableanggota data yang dimodifikasi (betapa kasarnya!).

Dalam std::function<> const&kasus ini, mutableanggota data yang dimodifikasi akan menyebar keluar dari pemanggilan fungsi. Dalam std::function<>kasus ini, mereka tidak akan melakukannya.

Ini adalah kasus sudut yang relatif aneh.

Anda ingin memperlakukan std::functionseperti Anda memperlakukan jenis lain yang mungkin berbobot berat dan dapat dipindahkan dengan murah. Pindahan itu murah, menyalin bisa mahal.


Keuntungan semantik dari "nilai lewat jika Anda menyimpannya", seperti yang Anda katakan, adalah bahwa dengan kontrak fungsi tidak dapat menyimpan alamat argumen yang diteruskan. Tapi apakah benar bahwa "Kecuali itu, sebuah langkah akan seefisien sebuah const &"? Saya selalu melihat biaya operasi penyalinan ditambah biaya operasi pemindahan. Dengan lewatnya const&saya hanya melihat biaya operasi penyalinan.
ceztko

2
@ceztko Dalam kedua kasus (A) dan (B), sementara std::functiondibuat dari lambda. Dalam (A), sementara dielaborasi menjadi argumen untuk run_in_ui_thread. Dalam (B) referensi ke sementara tersebut diteruskan ke run_in_ui_thread. Selama std::functions Anda dibuat dari lambda sebagai temporer, klausa itu berlaku. Paragraf sebelumnya membahas kasus di mana masih std::functionada. Jika kita tidak menyimpan, hanya membuat dari lambda, function const&dan functionmemiliki overhead yang sama persis.
Yakk - Adam Nevraumont

Ah, begitu! Ini tentu saja tergantung dari apa yang terjadi di luar run_in_ui_thread(). Apakah hanya ada tanda tangan yang mengatakan "Lewati referensi, tetapi saya tidak akan menyimpan alamatnya"?
ceztko

@ceztko Tidak, tidak ada.
Yakk - Adam Nevraumont

1
@ Yakk-AdamNevraumont jika akan lebih lengkap untuk menutupi opsi lain untuk melewati rvalue ref:std::future<void> run_in_ui_thread( std::function<void()>&& )
Pavel P

33

Jika Anda khawatir tentang kinerja, dan Anda tidak menentukan fungsi anggota virtual, kemungkinan besar Anda tidak boleh menggunakan std::functionsama sekali.

Membuat jenis functor sebagai parameter template memungkinkan pengoptimalan yang lebih besar daripada std::function, termasuk menyebariskan logika functor. Efek dari pengoptimalan ini kemungkinan besar jauh lebih besar daripada masalah copy-vs-indirection tentang cara mengoper std::function.

Lebih cepat:

template<typename Functor>
void callFunction(Functor&& x)
{
    x();
}

1
Sebenarnya saya tidak khawatir sama sekali tentang kinerja. Saya hanya berpikir menggunakan referensi const di mana mereka harus digunakan adalah praktik umum (string dan vektor muncul dalam pikiran).
Sven Adbring

15
@ Ben: Saya pikir cara paling modern yang ramah kaum hippie untuk menerapkan ini adalah dengan menggunakan std::forward<Functor>(x)();, untuk mempertahankan kategori nilai dari functor, karena ini adalah referensi "universal". Tidak akan membuat perbedaan dalam 99% kasus.
GManNickG

1
@ Ben Voigt jadi untuk kasus Anda, apakah saya akan memanggil fungsi dengan gerakan? callFunction(std::move(myFunctor));
arias_JC

2
@arias_JC: Jika parameternya adalah lambda, itu sudah menjadi nilai r. Jika Anda memiliki nilai l, Anda dapat menggunakan std::movejika Anda tidak lagi membutuhkannya dengan cara lain, atau meneruskan secara langsung jika Anda tidak ingin keluar dari objek yang ada. Aturan penciutan referensi memastikan bahwa callFunction<T&>()memiliki parameter bertipe T&, bukan T&&.
Ben Voigt

1
@BoltzmannBrain: Saya telah memilih untuk tidak membuat perubahan itu karena ini hanya berlaku untuk kasus yang paling sederhana, ketika fungsinya dipanggil hanya sekali. Jawaban saya adalah untuk pertanyaan "bagaimana cara mengirimkan objek fungsi?" dan tidak terbatas pada fungsi yang tidak melakukan apa pun selain memanggil fungsi itu tepat satu kali tanpa syarat.
Ben Voigt

27

Seperti biasa di C ++ 11, meneruskan value / reference / const-reference bergantung pada apa yang Anda lakukan dengan argumen Anda. std::functiontidak berbeda.

Melewati nilai memungkinkan Anda untuk memindahkan argumen ke dalam variabel (biasanya variabel anggota kelas):

struct Foo {
    Foo(Object o) : m_o(std::move(o)) {}

    Object m_o;
};

Ketika Anda tahu fungsi Anda akan memindahkan argumennya, ini adalah solusi terbaik, dengan cara ini pengguna Anda dapat mengontrol bagaimana mereka memanggil fungsi Anda:

Foo f1{Object()};               // move the temporary, followed by a move in the constructor
Foo f2{some_object};            // copy the object, followed by a move in the constructor
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor

Saya yakin Anda sudah mengetahui semantik dari (non) referensi konstanta jadi saya tidak akan mempermasalahkannya. Jika Anda ingin saya menambahkan lebih banyak penjelasan tentang ini, tanyakan saja dan saya akan memperbarui.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.