Saya terus mendengar banyak tentang functors di C ++. Dapatkah seseorang memberi saya gambaran umum tentang apa itu mereka dan dalam kasus apa mereka akan berguna?
876
Saya terus mendengar banyak tentang functors di C ++. Dapatkah seseorang memberi saya gambaran umum tentang apa itu mereka dan dalam kasus apa mereka akan berguna?
operator()(...)
artinya: itu berlebihan operator "panggilan fungsi" . Ini hanya kelebihan operator untuk()
operator. Jangan salahoperator()
dengan memanggil fungsi yang dipanggiloperator
, tetapi melihatnya sebagai sintaks berlebihan operator yang biasa.Jawaban:
Functor cukup banyak hanya kelas yang mendefinisikan operator (). Itu memungkinkan Anda membuat objek yang "mirip" fungsi:
Ada beberapa hal baik tentang functors. Salah satunya adalah bahwa tidak seperti fungsi biasa, mereka dapat berisi keadaan. Contoh di atas membuat fungsi yang menambahkan 42 ke apa pun yang Anda berikan. Tetapi nilai 42 itu bukan hardcoded, itu ditentukan sebagai argumen konstruktor ketika kami membuat instance functor kami. Saya dapat membuat penambah lain, yang menambahkan 27, hanya dengan memanggil konstruktor dengan nilai yang berbeda. Ini membuat mereka dapat dikustomisasi dengan baik.
Seperti yang ditampilkan baris terakhir, Anda sering memberikan functors sebagai argumen ke fungsi lain seperti std :: transform atau algoritma pustaka standar lainnya. Anda dapat melakukan hal yang sama dengan pointer fungsi biasa kecuali, seperti yang saya katakan di atas, functors dapat "dikustomisasi" karena mengandung keadaan, membuatnya lebih fleksibel (Jika saya ingin menggunakan pointer fungsi, saya harus menulis sebuah fungsi yang menambahkan tepat 1 ke argumennya.Functor adalah umum, dan menambahkan apa pun yang Anda inisialisasi dengan), dan mereka juga berpotensi lebih efisien. Dalam contoh di atas, kompiler tahu persis fungsi mana yang
std::transform
harus dipanggil. Seharusnya meneleponadd_x::operator()
. Itu berarti dapat inline panggilan fungsi itu. Dan itu membuatnya sama efisiennya dengan jika saya secara manual memanggil fungsi pada setiap nilai vektor.Jika saya telah melewatkan pointer fungsi, kompiler tidak dapat langsung melihat fungsi yang ditunjuknya, jadi kecuali ia melakukan beberapa optimasi global yang cukup kompleks, ia harus melakukan dereferensi pointer pada saat runtime, dan kemudian melakukan panggilan.
sumber
add42
, saya akan menggunakan functor yang saya buat sebelumnya, dan menambahkan 42 untuk setiap nilai. Denganadd_x(1)
saya membuat instance baru dari functor, yang hanya menambahkan 1 untuk setiap nilai. Ini hanya untuk menunjukkan bahwa sering, Anda instantiate functor "on the fly", ketika Anda membutuhkannya, daripada membuatnya terlebih dahulu, dan menyimpannya sebelum Anda benar-benar menggunakannya untuk apa pun.operator()
, karena itulah yang digunakan penelepon untuk memintanya. Apa lagi fungsi functor anggota, konstruktor, operator dan variabel anggota sepenuhnya terserah Anda.add42
akan disebut sebagai functor, bukanadd_x
(yang merupakan kelas dari functor atau hanya kelas functor). Saya menemukan bahwa terminologi konsisten karena functors juga disebut objek fungsi , bukan kelas fungsi. Bisakah Anda mengklarifikasi hal ini?Selain sedikit. Anda dapat menggunakan
boost::function
, untuk membuat functors dari fungsi dan metode, seperti ini:dan Anda dapat menggunakan boost :: bind untuk menambahkan status ke functor ini
dan yang paling berguna, dengan fungsi boost :: bind and boost :: Anda dapat membuat functor dari metode kelas, sebenarnya ini adalah delegasi:
Anda dapat membuat daftar atau vektor functors
Ada satu masalah dengan semua hal ini, pesan kesalahan kompilator tidak dapat dibaca manusia :)
sumber
operator ()
boleh publik dalam contoh pertama Anda karena kelas default untuk pribadi?Functor adalah objek yang bertindak seperti fungsi. Pada dasarnya, kelas yang mendefinisikan
operator()
.Keuntungan sebenarnya adalah bahwa functor dapat memegang status.
sumber
int
padahal seharusnya kembalibool
? Ini adalah C ++, bukan C. Ketika jawaban ini ditulis, apakahbool
tidak ada?Nama "functor" telah secara tradisional digunakan dalam teori kategori jauh sebelum C ++ muncul di tempat kejadian. Ini tidak ada hubungannya dengan konsep C ++ dari functor. Lebih baik menggunakan objek fungsi nama daripada apa yang kita sebut "functor" di C ++. Ini adalah bagaimana bahasa pemrograman lain memanggil konstruksi yang sama.
Digunakan sebagai ganti fungsi biasa:
Fitur:
Cons:
Digunakan alih-alih fungsi pointer:
Fitur:
Cons:
Digunakan sebagai ganti fungsi virtual:
Fitur:
Cons:
sumber
foo(arguments)
. Oleh karena itu, dapat berisi variabel; misalnya, jika Anda memilikiupdate_password(string)
fungsi, Anda mungkin ingin melacak seberapa sering itu terjadi; dengan functor, yang dapatprivate long time
mewakili cap waktu yang terakhir terjadi. Dengan pointer fungsi atau fungsi polos, Anda akan perlu menggunakan luar variabel namespace, yang hanya berhubungan langsung dengan dokumentasi dan penggunaan, bukan oleh definition.lSeperti yang telah disebutkan orang lain, functor adalah objek yang bertindak seperti fungsi, yaitu overload operator fungsi panggilan.
Functors biasanya digunakan dalam algoritma STL. Mereka berguna karena mereka dapat menahan keadaan sebelum dan di antara panggilan fungsi, seperti penutupan dalam bahasa fungsional. Misalnya, Anda bisa mendefinisikan
MultiplyBy
functor yang mengalikan argumennya dengan jumlah yang ditentukan:Kemudian Anda bisa meneruskan
MultiplyBy
objek ke algoritma seperti std :: transform:Keuntungan lain dari functor atas penunjuk ke suatu fungsi adalah bahwa panggilan dapat digarisbawahi dalam lebih banyak kasus. Jika Anda melewati fungsi pointer ke
transform
, kecuali bahwa panggilan mendapat inline dan compiler tahu bahwa Anda selalu melewati fungsi yang sama untuk itu, tidak dapat inline panggilan melalui pointer.sumber
Untuk pemula seperti saya di antara kita: setelah sedikit riset saya menemukan apa yang dilakukan oleh kode jalf.
Functor adalah objek kelas atau struct yang dapat "dipanggil" seperti fungsi. Ini dimungkinkan dengan overloading
() operator
. The() operator
(tidak yakin apa yang disebut) dapat mengambil sejumlah argumen. Operator lain hanya mengambil dua yaitu+ operator
hanya dapat mengambil dua nilai (satu di setiap sisi operator) dan mengembalikan nilai apa pun yang Anda miliki kelebihannya. Anda dapat memasukkan sejumlah argumen di dalam() operator
apa yang memberikan fleksibilitas.Untuk membuat functor, pertama Anda membuat kelas Anda. Kemudian Anda membuat konstruktor ke kelas dengan parameter pilihan jenis dan nama. Ini diikuti dalam pernyataan yang sama dengan daftar penginisialisasi (yang menggunakan satu operator titik dua, sesuatu yang saya juga baru) yang membangun objek anggota kelas dengan parameter yang dinyatakan sebelumnya ke konstruktor. Kemudian
() operator
kelebihan beban. Akhirnya Anda mendeklarasikan objek pribadi dari kelas atau struct yang telah Anda buat.Kode saya (saya menemukan nama variabel jalf membingungkan)
Jika semua ini tidak akurat atau hanya salah, jangan ragu untuk mengoreksi saya!
sumber
Functor adalah fungsi orde tinggi yang menerapkan fungsi untuk tipe parametrized (yaitu templated). Ini adalah generalisasi dari fungsi peta tingkat tinggi. Sebagai contoh, kita dapat mendefinisikan functor untuk
std::vector
seperti ini:Fungsi ini mengambil
std::vector<T>
dan mengembalikanstd::vector<U>
ketika diberi fungsiF
yang mengambilT
dan mengembalikan aU
. Sebuah functor tidak harus didefinisikan lebih dari tipe kontainer, itu dapat didefinisikan untuk tipe templated juga, termasukstd::shared_ptr
:Inilah contoh sederhana yang mengubah tipe menjadi
double
:Ada dua hukum yang harus diikuti oleh para pelaku. Yang pertama adalah hukum identitas, yang menyatakan bahwa jika functor diberikan fungsi identitas, itu harus sama dengan menerapkan fungsi identitas ke tipe, yaitu
fmap(identity, x)
harus sama denganidentity(x)
:Hukum berikutnya adalah hukum komposisi, yang menyatakan bahwa jika functor diberi komposisi dua fungsi, itu harus sama dengan menerapkan functor untuk fungsi pertama dan sekali lagi untuk fungsi kedua. Jadi,
fmap(std::bind(f, std::bind(g, _1)), x)
harus sama denganfmap(f, fmap(g, x))
:sumber
fmap(id, x) = id(x)
danfmap(f ◦ g, x) = fmap(f, fmap(g, x))
.Inilah situasi aktual di mana saya dipaksa menggunakan Functor untuk menyelesaikan masalah saya:
Saya memiliki serangkaian fungsi (katakanlah, 20 di antaranya), dan semuanya identik, kecuali masing-masing memanggil fungsi spesifik yang berbeda di 3 titik tertentu.
Ini adalah pemborosan yang luar biasa, dan duplikasi kode. Biasanya saya hanya akan melewatkan fungsi pointer, dan sebut saja di 3 tempat. (Jadi kodenya hanya perlu muncul sekali, bukan dua puluh kali.)
Tetapi kemudian saya menyadari, dalam setiap kasus, fungsi spesifik membutuhkan profil parameter yang sama sekali berbeda! Terkadang 2 parameter, terkadang 5 parameter, dll.
Solusi lain akan memiliki kelas dasar, di mana fungsi spesifik adalah metode yang ditimpa dalam kelas turunan. Tetapi apakah saya benar-benar ingin membangun semua Warisan ini, supaya saya dapat melewatkan fungsi pointer ????
SOLUSI: Jadi yang saya lakukan adalah, saya membuat kelas pembungkus ("Functor") yang dapat memanggil salah satu fungsi yang saya butuhkan dipanggil. Saya mengaturnya terlebih dahulu (dengan parameternya, dll) dan kemudian saya meneruskannya sebagai ganti fungsi pointer. Sekarang kode yang dipanggil dapat memicu Functor, tanpa mengetahui apa yang terjadi di dalam. Ia bahkan dapat memanggilnya beberapa kali (saya membutuhkannya untuk menelepon 3 kali.)
Itu saja - contoh praktis di mana Functor ternyata menjadi solusi yang jelas dan mudah, yang memungkinkan saya untuk mengurangi duplikasi kode dari 20 fungsi menjadi 1.
sumber
Kecuali untuk digunakan dalam panggilan balik, fungsi C ++ juga dapat membantu menyediakan gaya akses yang disukai Matlab ke kelas matriks . Ada sebuah contoh .
sumber
operator()
tetapi tidak memanfaatkan properti fungsi objek.Seperti telah diulang, functors adalah kelas yang dapat diperlakukan sebagai fungsi (operator overload ()).
Mereka paling berguna untuk situasi di mana Anda perlu mengaitkan beberapa data dengan panggilan berulang atau tertunda ke suatu fungsi.
Misalnya, daftar fungsi yang ditautkan dapat digunakan untuk mengimplementasikan sistem koroutin sinkron overhead rendah dasar, dispatcher tugas, atau penguraian file yang interruptable. Contoh:
Tentu saja, contoh-contoh ini tidak begitu berguna dalam diri mereka. Mereka hanya menunjukkan bagaimana functors dapat berguna, functors itu sendiri sangat mendasar dan tidak fleksibel dan ini membuat mereka kurang bermanfaat daripada, misalnya, apa yang disediakan boost.
sumber
Functors digunakan dalam gtkmm untuk menghubungkan beberapa tombol GUI ke fungsi atau metode C ++ yang sebenarnya.
Jika Anda menggunakan pthread library untuk membuat aplikasi Anda multithreaded, Functors dapat membantu Anda.
Untuk memulai utas, salah satu argumen
pthread_create(..)
adalah penunjuk fungsi yang akan dieksekusi pada utasnya sendiri.Tapi ada satu ketidaknyamanan. Pointer ini tidak bisa menjadi pointer ke metode, kecuali metode statis , atau kecuali Anda menentukan kelasnya , seperti
class::method
. Dan satu hal lagi, antarmuka metode Anda hanya dapat:Jadi Anda tidak dapat menjalankan (dengan cara yang jelas dan sederhana), metode dari kelas Anda di utas tanpa melakukan sesuatu yang ekstra.
Cara yang sangat baik untuk berurusan dengan utas di C ++, adalah membuat
Thread
kelas Anda sendiri . Jika Anda ingin menjalankan metode dariMyClass
kelas, apa yang saya lakukan adalah, ubah metode tersebut menjadiFunctor
kelas turunan.Juga,
Thread
kelas memiliki metode ini:static void* startThread(void* arg)
Penunjuk ke metode ini akan digunakan sebagai argumen untuk memanggil
pthread_create(..)
. Dan apa yangstartThread(..)
harus diterima dalam arg adalahvoid*
referensi yang dicor ke instance di heap dari setiapFunctor
kelas turunan, yang akan dilemparkan kembali keFunctor*
saat dieksekusi, dan kemudian disebutrun()
metode itu.sumber
Untuk menambahkan, saya telah menggunakan objek fungsi agar sesuai dengan metode warisan yang ada dengan pola perintah; (satu-satunya tempat di mana keindahan paradigma OO benar OCP saya rasakan); Juga menambahkan di sini pola adaptor fungsi terkait.
Misalkan metode Anda memiliki tanda tangan:
Kita akan melihat bagaimana kita dapat menyesuaikannya dengan pola Command - untuk ini, pertama, Anda harus menulis adaptor fungsi anggota sehingga dapat dipanggil sebagai objek fungsi.
Catatan - ini jelek, dan mungkin Anda bisa menggunakan Boost bind helper dll, tetapi jika Anda tidak bisa atau tidak mau, ini adalah satu cara.
Juga, kita membutuhkan metode helper mem_fun3 untuk kelas di atas untuk membantu dalam memanggil.
}
Sekarang, untuk mengikat parameter, kita harus menulis fungsi binder. Jadi, ini dia:
Dan, fungsi pembantu untuk menggunakan kelas binder3 - bind3:
Sekarang, kita harus menggunakan ini dengan kelas Command; gunakan typedef berikut:
Ini adalah bagaimana Anda menyebutnya:
Catatan: f3 (); akan memanggil metode task1-> ThreeParameterTask (21,22,23) ;.
Konteks penuh dari pola ini pada tautan berikut
sumber
Keuntungan besar dari penerapan fungsi sebagai functors adalah mereka dapat mempertahankan dan menggunakan kembali keadaan di antara panggilan. Sebagai contoh, banyak algoritma pemrograman dinamis, seperti algoritma Wagner-Fischer untuk menghitung jarak Levenshtein antara string, bekerja dengan mengisi tabel besar hasil. Sangat tidak efisien untuk mengalokasikan tabel ini setiap kali fungsi dipanggil, jadi mengimplementasikan fungsi sebagai functor dan menjadikan tabel sebagai variabel anggota dapat sangat meningkatkan kinerja.
Di bawah ini adalah contoh penerapan algoritma Wagner-Fischer sebagai functor. Perhatikan bagaimana tabel dialokasikan di konstruktor, dan kemudian digunakan kembali
operator()
, dengan mengubah ukuran sesuai kebutuhan.sumber
Functor juga dapat digunakan untuk mensimulasikan mendefinisikan fungsi lokal dalam suatu fungsi. Lihat pertanyaan dan lainnya .
Tetapi functor lokal tidak dapat mengakses variabel otomatis di luar. Fungsi lambda (C ++ 11) adalah solusi yang lebih baik.
sumber
Saya telah "menemukan" penggunaan functors yang sangat menarik: Saya menggunakannya ketika saya tidak memiliki nama yang bagus untuk satu metode, karena functor adalah metode tanpa nama ;-)
sumber