Sabit başvuru ile bir std :: işlevini geçmeli miyim?


141

Diyelim ki aşağıdakileri alan bir fonksiyonum var std::function:

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

Bunun xyerine const-reference ile geçmeli miyim ? :

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

Bu sorunun cevabı, işlevin onunla ne yaptığına bağlı olarak değişiyor mu? Örneğin std::function, bir üye değişkeni içine saklayan veya başlatan bir sınıf üyesi işlevi veya kurucusuysa .


1
Muhtemelen değil. Emin değilim, ama sizeof(std::function)daha fazla olmayı beklerdim 2 * sizeof(size_t), bu bir const referansı için düşündüğünüz en az boyut.
Mats Petersson

12
@Mats: Sargının boyutunun std::functionkopyalamanın karmaşıklığı kadar önemli olduğunu düşünmüyorum . Derin kopyalar varsa, sizeofönerdiğinden çok daha pahalı olabilir .
Ben Voigt

moveİşlevi yerine getirmeli misiniz ?
Yakk - Adam Nevraumont

operator()()olan constbir sabit başvuru çalışması gerekir, böylece. Ama std :: fonksiyonunu hiç kullanmadım.
Neel Basu

@Yakk Lambda'yı doğrudan işleve geçiriyorum.
Sven Adbring

Yanıtlar:


79

Performans istiyorsanız, saklıyorsanız değere göre geçin.

"Bunu UI iş parçacığında çalıştır" adında bir işleve sahip olduğunuzu varsayalım.

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

"ui" iş parçacığında bazı kodlar çalıştırılır, sonra futurene zaman yapıldığına işaret eder . (UI iş parçacığının UI öğeleriyle uğraşmanız gereken yerlerde UI çerçevelerinde kullanışlıdır)

Düşündüğümüz iki imzamız var:

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

Şimdi bunları muhtemelen şu şekilde kullanacağız:

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

anonim bir kapatma (lambda) oluşturacak, bir std::functiondışarı oluşturacak, run_in_ui_threadişleve geçirecek ve daha sonra ana iş parçacığında çalışmasını bitirmesini bekleyecektir.

(A) durumunda std::function, doğrudan lambdadan yapılır, daha sonra içinde kullanılır run_in_ui_thread. Lambda, moveiçine girer std::function, bu nedenle herhangi bir hareketli durum etkili bir şekilde içine taşınır.

İkinci durumda, bir geçici std::functionyaratılır, lambda moveiçine girilir , daha sonra bu geçici std::function, içinde referans olarak kullanılır run_in_ui_thread.

Şimdiye kadar, çok iyi - ikisi aynı performans gösteriyor. Dışında run_in_ui_threadişlevi argüman bir kopyasını yapacak yürütmek için ui iplik göndermek için! (onunla yapılmadan önce geri dönecektir, bu yüzden sadece bir referans kullanamaz). Durumda, (A) için, basitçe uzun vadeli depoya. (B) durumunda, kopyalamak zorunda kalırız .movestd::functionstd::function

Bu mağaza, değere göre geçişi daha uygun hale getirir. Bir kopyasını saklamak için herhangi bir olasılık varsa, std::functiondeğere göre geçin. Aksi takdirde, her iki yol da kabaca eşdeğerdir: by-value için tek dezavantaj, aynı hantal std::functionalıp başka bir kullanımdan sonra bir alt yönteme sahip olmanızdır . Bunu engellemek, a movekadar etkili olacaktır const&.

Şimdi, ikisinin arasında, içinde kalıcı bir durumumuz varsa çoğunlukla devreye giren başka bazı farklılıklar var std::function.

std::functionBir nesneyi a ile sakladığını operator() const, ancak mutabledeğiştirdiği (ne kadar kaba!) Bazı veri üyelerine sahip olduğunu varsayın .

Bu std::function<> const&durumda, mutabledeğiştirilen veri üyeleri işlev çağrısından yayılır. In std::function<>durumunda, bunlar olmaz.

Bu nispeten garip bir köşe davası.

std::functionMuhtemelen ağır, ucuza hareket eden başka bir şeymiş gibi davranmak istersiniz. Taşınma ucuz, kopyalama pahalı olabilir.


Söylediğiniz gibi "eğer saklıyorsanız değere göre geçin" anlamsal avantajı, işlevin sözleşmeyle geçilen argümanın adresini tutamamasıdır. Ama "Bunu yasaklamak, bir hareketin bir const kadar verimli olacağı doğru mu?" Her zaman bir kopyalama işleminin maliyetini ve taşıma işleminin maliyetini görürüm. Geçerken const&sadece kopyalama işleminin maliyetini görüyorum.
ceztko

2
@ceztko Hem (A) hem de (B) durumlarda geçici std::function, lambdadan oluşturulur. (A) 'da, geçici, argümanına ayrılır run_in_ui_thread. (B) 'de adı geçen geçiciye bir referans aktarılır run_in_ui_thread. Sizin std::functions lambdaslardan geçici olarak yaratıldığı sürece , bu madde geçerlidir. Önceki paragraf std::functiondevam ettiği dava ile ilgilidir . Biz ise verilmez depolanması, sadece bir lambda oluşturma function const&ve functionaynı yükü vardır.
Yakk - Adam Nevraumont

Ah, anlıyorum! Bu elbette dışında neler olduğuna bağlıdır run_in_ui_thread(). "Referans ile geç, ama adresi saklamıyorum" demek için sadece bir imza var mı?
ceztko

@ceztko Hayır, yok.
Yakk - Adam Nevraumont

1
@ Yakk-AdamNevraumont, rvalue ref'den geçmek için başka bir seçeneği kapsamak için daha eksiksiz olurdu:std::future<void> run_in_ui_thread( std::function<void()>&& )
Pavel P

33

Performans konusunda endişeleniyorsanız ve bir sanal üye işlevi tanımlamıyorsanız, büyük olasılıkla hiç kullanmamalısınız std::function.

Functor türünü şablon parametresi yapmak, std::functionfunctor mantığını satır içine almak da dahil olmak üzere daha fazla optimizasyon sağlar . Bu optimizasyonların etkisinin, nasıl geçileceğine ilişkin kopyalama-dolaylı kaygılarından büyük ölçüde ağır basması muhtemeldir std::function.

Daha hızlı:

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

1
Aslında performans konusunda hiç endişelenmiyorum. Ben sadece const-referansları nerede kullanılması gerektiğini düşündüm yaygın uygulama (dizeleri ve vektörler akla geliyor).
Sven Adbring

13
@Ben: Bence bunu uygulamanın en modern hippi dostu yolu, std::forward<Functor>(x)();"evrensel" bir referans olduğu için fonksiyonun değer kategorisini korumaktır. Yine de vakaların% 99'unda fark yaratmayacak.
GManNickG

1
@Ben Voigt, bu yüzden davanız için işlevi bir hamle ile arayabilir miyim? callFunction(std::move(myFunctor));
arias_JC

2
@arias_JC: Parametre lambda ise, zaten bir değerdir. Bir std::movedeğeriniz varsa, artık başka bir şekilde ihtiyacınız olmayacaksa kullanabilirsiniz veya mevcut nesneden çıkmak istemiyorsanız doğrudan geçebilirsiniz. Başvuru daraltma kuralları callFunction<T&>()tür parametresine sahip olmasını sağlar T&, değil T&&.
Ben Voigt

1
@BoltzmannBrain: Bu değişikliği yapmamayı seçtim çünkü fonksiyon sadece bir kez çağrıldığında en basit durum için geçerlidir. Cevabım, "bir fonksiyon nesnesini nasıl iletmeliyim?" ve koşulsuz olarak hiçbir şey yapmayan bir işlevle sınırlı olmamak kaydıyla, bu işlevi tam olarak bir kez çağırır.
Ben Voigt

25

C ++ 11'de her zamanki gibi, value / reference / const-reference'den geçmek argümanınızla ne yaptığınıza bağlıdır. std::functionfarklı değil.

Değere göre iletme , bağımsız değişkeni bir değişkene (genellikle bir sınıfın üye değişkeni) taşımanıza olanak tanır:

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

    Object m_o;
};

İşlevinizin argümanını taşıyacağını bildiğinizde, bu en iyi çözümdür, bu şekilde kullanıcılarınız işlevinizi nasıl çağırdıklarını kontrol edebilir:

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

Ben zaten const referanslarının (anlamsız) anlamlarını bildiğinize inanıyorum, bu yüzden konuyu işaretlemeyeceğim. Bununla ilgili daha fazla açıklama eklemem gerekiyorsa, sormanız yeterlidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.