C ++ 'da bir geri çağırma işlevi uygularken, yine de C stili işlev işaretçisini kullanmalıyım:
void (*callbackFunc)(int);
Yoksa std :: fonksiyonunu kullanmalıyım:
std::function< void(int) > callbackFunc;
C ++ 'da bir geri çağırma işlevi uygularken, yine de C stili işlev işaretçisini kullanmalıyım:
void (*callbackFunc)(int);
Yoksa std :: fonksiyonunu kullanmalıyım:
std::function< void(int) > callbackFunc;
Yanıtlar:
Kısacası,std::function
bir nedeniniz olmadığı sürece kullanın .
İşlev işaretçileri bazı bağlamları yakalayamama dezavantajına sahiptir . Örneğin, bazı bağlam değişkenlerini yakalayan bir geri arama olarak lambda işlevini geçiremezsiniz (ancak yakalamadığında işe yarayacaktır). Bu nedenle, bir nesnenin üye değişkenini (yani statik olmayan) this
çağırmak da mümkün değildir, çünkü nesnenin ( -pointer) yakalanması gerekir. (1)
std::function
(çünkü C ++ 11) öncelikle bir işlevi saklamak içindir (onu aktarmak, saklanmasını gerektirmez). Dolayısıyla, geri aramayı örneğin bir üye değişkeninde saklamak istiyorsanız, muhtemelen en iyi seçimdir. Ama aynı zamanda saklamıyorsanız, iyi bir "ilk seçim" olmasına rağmen, çağrıldığında bazı (çok küçük) ek yükü dezavantajına sahip olsa da (çok performans açısından kritik bir durumda bir sorun olabilir, ancak çoğu olmamalıdır). Çok "evrensel": tutarlı ve okunabilir kodlara çok önem veriyorsanız ve yaptığınız her seçimi (yani basit tutmak istiyorsanız) düşünmek istemiyorsanız, std::function
etrafta dolaştığınız her fonksiyon için kullanın .
Üçüncü bir seçenek düşünün: o zaman sağlanan geri arama fonksiyonu vasıtasıyla şey bildiriyor küçük işlevi uygulamak için yaklaşık iseniz, bir düşünün şablon parametresi sonra olabilir, herhangi bir çağrılabilir nesne , yani bir işlev işaretçisi, bir funktoru, bir lambda, a std::function
, ... Buradaki dezavantaj, (dış) işlevinizin bir şablon haline gelmesi ve dolayısıyla başlıkta uygulanması gerektiğidir. Öte yandan, (dış) fonksiyonunuzun istemci kodu, geri çağrıya yapılan çağrının tam olarak kullanılabilecek tür bilgisi olacağını "gördüğü" için, geri çağrıya yapılan çağrının satır içine alınabilmesi avantajını elde edersiniz.
Template parametresine sahip sürüm örneği ( C ++ 11 öncesi &
yerine yazma &&
):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
Aşağıdaki tabloda görebileceğiniz gibi, hepsinin avantajları ve dezavantajları vardır:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) Bu sınırlamanın üstesinden gelmek için geçici çözümler mevcuttur, örneğin ek verileri (dış) fonksiyonunuza başka parametreler olarak iletmek: myFunction(..., callback, data)
çağrılacaktır callback(data)
. C ++ 'da (ve bu arada WIN32 API'de yoğun bir şekilde kullanılırken) mümkün olan ancak C ++' da daha iyi seçeneklere sahip olduğumuzdan kaçınılması gereken C tarzı "argümanlarla geri çağrı".
(2) Bir sınıf şablonundan bahsetmediğimiz sürece, yani işlevi sakladığınız sınıf bir şablondur. Ancak bu, istemci tarafında, işlevin türünün, gerçek kullanım durumları için neredeyse hiçbir zaman bir seçenek olmayan geri çağrıyı depolayan nesnenin türüne karar verdiği anlamına gelir.
(3) C ++ 11 öncesi için boost::function
std::function
hemen dışında saklamanız gerekiyorsa , tür silinmiş olana dönüştürülebilen etkin şablon işlevini yaratabilirsiniz. bağlam denir).
void (*callbackFunc)(int);
C tarzı geri arama işlevi olabilir, ancak kötü tasarımın korkunç derecede kullanılamaz bir işlevidir.
İyi tasarlanmış bir C tarzı geri arama, şuna benzer void (*callbackFunc)(void*, int);
- geri çağrıyı void*
yapan kodun, fonksiyonun ötesinde durumunu korumasına izin vermek için bir vardır . Bunu yapmamak, arayanı devleti küresel olarak depolamaya zorlar, bu da kaba değildir.
std::function< int(int) >
sonuç int(*)(void*, int)
olarak çoğu uygulamada çağrıdan biraz daha pahalı olur . Ancak bazı derleyicilerin satır içi olması daha zordur. Orada std::function
rakip fonksiyon işaretçisi invokation giderleri kütüphaneler içine kendi yolunu yapabilir ( 'mümkün olan en hızlı delegelere' vb bakınız) o klon uygulamalar.
Şimdi, bir geri arama sistemi istemcilerinin, geri arama oluşturulduğunda ve kaldırıldığında kaynakları kurmaları ve atmaları ve geri arama ömrünün farkında olmaları gerekir. void(*callback)(void*, int)
bunu sağlamaz.
Bazen bu, kod yapısı (geri aramanın ömrü sınırlıdır) veya diğer mekanizmalar (geri aramaları ve benzerlerini silme) yoluyla kullanılabilir.
std::function
sınırlı ömür boyu yönetim için bir araç sağlar (nesnenin son kopyası unutulduğunda kaybolur).
Genel olarak, std::function
performans kaygıları ortaya çıkmadıkça bir . Eğer öyleyse, ilk önce yapısal değişiklikleri arardım (piksel başına geri arama yerine, bana geçtiğiniz lambda tabanlı bir tarama hattı işlemcisi üretmeye ne dersiniz? ). Sonra, devam ederse, delegate
mümkün olan en hızlı delegelere dayalı bir yazı yazardım ve performans sorununun giderilip giderilmediğini görüyorum.
Çoğunlukla yalnızca eski API'lar için işlev işaretçileri veya farklı derleyiciler oluşturulan kodlar arasında iletişim kurmak için C arabirimleri oluşturmak için kullanılır. Ayrıca atlama tabloları, tip silme, vb. Uygularken bunları iç uygulama ayrıntıları olarak kullandım: hem üretip hem de tüketirken ve dışarıdan herhangi bir istemci kodunun kullanması için dışarıda açığa çıkmadığımda ve işlev işaretçileri ihtiyacım olan her şeyi yapıyor .
Uygun geri çağrı ömür boyu yönetim altyapısı olduğu varsayılarak, std::function<int(int)>
bir int(void*,int)
stil geri aramasına dönüşen sarmalayıcılar yazabileceğinizi unutmayın . Bu yüzden herhangi bir C tarzı geri arama ömür boyu yönetim sistemi için bir duman testi olarak, bir sarma sarma std::function
makul iyi çalışır emin olun .
void*
nereden geldi? Durumu neden işlevin ötesinde tutmak istersiniz? Bir işlev, ihtiyaç duyduğu tüm kodu, tüm işlevleri içermelidir, sadece istenen bağımsız değişkenleri iletir ve bir şeyi değiştirir ve döndürürsünüz. Bazı harici durumlara ihtiyacınız varsa neden bir functionPtr veya geri arama bu bagajı taşıyacaktır? Ben geri arama gereksiz karmaşık olduğunu düşünüyorum.
this
. Bir üye işlev çağrılması durumunda hesaplamak zorunda olduğu için, bu yüzden this
nesnenin adresini işaret etmek için işaretçiye ihtiyacımız var mı? Eğer yanılıyorsam, bu konuda daha fazla bilgi bulabileceğim bir bağlantı verebilir misiniz, çünkü bu konuda fazla bir şey bulamıyorum. Şimdiden teşekkürler.
void*
, çalışma zamanı durumunun iletilmesine izin vermek için bir alır . A void*
ve void*
bağımsız değişkeni olan bir işlev işaretçisi bir nesneye üye işlev çağrısını taklit edebilir. Maalesef, "C geri arama mekanizmaları 101 tasarlama" yolunda ilerleyen bir kaynak bilmiyorum.
this
. Demek istediğim şey o. Tamam, yine de teşekkürler.
std::function
İsteğe bağlı çağrılabilir nesneleri saklamak için kullanın . Kullanıcının geri arama için gereken bağlamı sağlamasına olanak tanır; düz işlev işaretçisi bunu yapmaz.
Herhangi bir nedenden ötürü düz işlev işaretçileri kullanmanız gerekiyorsa (belki de C uyumlu bir API istediğiniz için), bir void * user_context
argüman eklemeniz gerekir; böylece, doğrudan işlevi.
Bundan kaçınmanın tek nedeni std::function
, C ++ 11'de tanıtılan bu şablon için destek bulunmayan eski derleyicilerin desteğidir.
C ++ 11 öncesi dili desteklemek bir zorunluluk değilse, kullanmak std::function
, arayanlarınıza geri çağrıyı uygulamada daha fazla seçenek sunarak "düz" işlev işaretçileriyle karşılaştırıldığında daha iyi bir seçenek haline getirir. API'nızın kullanıcılarına daha fazla seçenek sunarken, geri aramayı gerçekleştiren kodunuz için uygulamalarının özelliklerini de soyutlar.