Bir paylaşılan_ptr değerini referans veya değere göre geçmeli miyiz?


270

Bir işlev bir shared_ptr(boost veya C ++ 11 STL'den) aldığında , geçiyor musunuz:

  • const referansı ile: void foo(const shared_ptr<T>& p)

  • veya değerine göre: void foo(shared_ptr<T> p)?

İlk yöntemi tercih ederim çünkü daha hızlı olacağından şüpheleniyorum. Ama bu gerçekten buna değer mi yoksa başka sorunlar var mı?

Lütfen seçiminizin nedenlerini veya durumun neden önemli olmadığını düşündüğünüzü açıklar mısınız?


14
Sorun şu ki eşdeğer değiller. Referans sürümü "Bazılarına takma ad veriyorum shared_ptrve eğer istersem değiştirebilirim " diye bağırıyor shared_ptr. ) bir const referans parametresi diyor gerçek çözümdür "biraz takma gidiyorum shared_ptrve bunu değiştirmek için söz veriyorum." (by-value semantik son derece benzerdir hangisi)!
GManNickG

2
Hey, bir sınıf üyesini iade etme konusunda görüşlerinizi merak ediyorum shared_ptr. Const-refs ile mi yapıyorsunuz?
Johannes Schaub - litb

Üçüncü olasılık C ++ 0x ile std :: move () kullanmak, bu hem
paylaşılan_ptr

@Johannes: Herhangi bir kopyalama / ref sayımından kaçınmak için const referansıyla iade ederim. Sonra tekrar, ilkel olmadıkça tüm üyeleri const-reference ile geri gönderirim.
GManNickG

Yanıtlar:


229

Bu soru Scott, Andrei ve Herb tarafından C ++ ve Ötesinde 2011'de Bize Sorun oturumu sırasında tartışılmış ve cevaplanmıştır . 4:34 dan İzle üzerinde performans ve doğruluğu .shared_ptr

Kısacası, amaç bir nesnenin sahipliğini (örneğin farklı veri yapıları arasında veya farklı evreler arasında) paylaşmak olmadıkça değere göre geçmek için bir neden yoktur .

Yukarıda bağlantılı konuşma videosunda Scott Meyers tarafından açıklandığı gibi taşıyabilir veya optimize edemezseniz, ancak bu, kullanabileceğiniz C ++ 'ın gerçek sürümüyle ilgilidir.

Bu tartışmaya önemli bir güncelleştirme sırasında yaşandı GoingNative 2012 Konferansın İnteraktif Panel: Bize şey sor! izlemeye değer, özellikle de 22:50 .


5
ancak burada gösterildiği gibi değere göre geçmek daha ucuzdur: stackoverflow.com/a/12002668/128384 de dikkate alınmamalıdır (en azından bir paylaşılan_ptr öğesinin üye olacağı yapımcı argümanları vb. için) sınıf)?
stijn

2
@stijn Evet ve hayır. Belirttiğiniz C ++ standardının sürümünü netleştirmedikçe, işaret ettiğiniz S ve C eksik. Sadece yanıltıcı olan genel / her zaman kuralların yayılması çok kolaydır. Okurlar David Abrahams'ın makalelerini ve referanslarını tanımak için zaman ayırmazlar veya yayınlanma tarihini mevcut C ++ standardına göre hesaba katmazlar. Bu yüzden, hem benim hem de işaret ettiğiniz yanıtların her ikisi de, gönderme zamanı göz önüne alındığında doğrudur.
mloskot

1
" çoklu iş parçacığı olmadığı sürece " hayır, MT hiçbir şekilde özel değildir.
curiousguy

3
Partiye süper geç kaldım, ama paylaşılan_ptr değerini değere göre geçmek istemem, kodu daha kısa ve daha güzel hale getirmesidir. Ciddi anlamda. Value*kısa ve okunabilir, ancak kötü, bu yüzden şimdi kodum dolu const shared_ptr<Value>&ve daha az okunabilir ve sadece ... daha az düzenli. Ne eskiden void Function(Value* v1, Value* v2, Value* v3)şimdi void Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)ve insanlar bu konuda iyi misin?
Alex

7
@Alex Ortak uygulama, dersten hemen sonra takma adlar (typedefs) oluşturmaktır. Örneğiniz için: class Value {...}; using ValuePtr = std::shared_ptr<Value>;O zaman işleviniz daha basit hale gelir: void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)ve maksimum performans elde edersiniz. Bu yüzden C ++ kullanıyorsunuz, değil mi? :)
4LegsDrivenCat

92

İşte Herb Sutter'in alımı

Kılavuz: Sahipliği paylaşmak veya aktarmak gibi akıllı işaretçiyi kullanmak veya değiştirmek istemiyorsanız, akıllı işaretçiyi işlev parametresi olarak geçirmeyin.

Yönerge: Bir işlevin bir yığın-değer paylaşılan_ptr parametresi kullanarak bir yığın nesnesinin sahipliğini depolayıp paylaşacağını ifade edin.

Yönerge: Yalnızca paylaşılan_ptr değerini değiştirmek için const olmayan bir paylaşılan_ptr ve parametresi kullanın. Const shared_ptr & parametresini yalnızca kopyasını alıp paylaşmayacağınızdan emin değilseniz ve sahiplik paylaşıp paylaşmayacağınız konusunda parametre olarak kullanın; aksi halde widget * kullanın (veya nullable değilse bir widget & kullanın).


3
Sutter bağlantısı için teşekkürler. Mükemmel bir makale. C ++ 14 mevcutsa, isteğe bağlı <widget &> tercih ederek widget * üzerinde ona katılmıyorum. widget * eski koddan çok belirsiz.
İsimsiz

3
Widget * ve widget & olasılıkları dahil etmek için +1. Sadece detaylandırmak için, widget * veya widget'ı geçmek ve fonksiyon işaretçi nesnesinin kendisini incelemediğinde / değiştirmediğinde muhtemelen en iyi seçenektir. Belirli bir işaretçi türü gerektirmediğinden ve paylaşılan_ptr başvuru sayısının performans sorunu atlatıldığından, arabirim daha geneldir.
tgnottingham

4
Bence bu ikinci rehberden dolayı kabul edilen cevap olmalı. Şu an kabul edilen cevabı açıkça geçersiz kılıyor, diyor ki: değere göre geçmek için bir neden yok.
mbrt

62

Şahsen bir constreferans kullanırdım . Bir işlev çağrısı uğruna sadece azaltmak için referans sayısını artırmaya gerek yoktur.


1
Cevabınızı düşürmedim, ancak bu bir tercih meselesi olmadan, dikkate alınması gereken iki olasılıktan her birinin artıları ve eksileri var. Ve artıları ve eksileri bu tezleri bilmek ve tartışmak iyi olur. Daha sonra herkes kendisi için bir karar verebilir.
Danvil

@Danvil: nasıl shared_ptrçalıştığını göz önünde bulundurarak, referansla geçmemenin tek dezavantajı performansta hafif bir kayıptır. Burada iki sebep var. a) işaretçi takma özelliği, veri değerine sahip işaretçiler artı bir sayaç (zayıf referanslar için belki 2) kopyalanır, bu nedenle veri turunu kopyalamak biraz daha pahalıdır. b) atomik referans sayımı, düz eski artış / azalma kodundan biraz daha yavaştır, ancak iş parçacığının güvenli olması için gereklidir. Bunun ötesinde, iki yöntem çoğu amaç ve amaç için aynıdır.
Evan Teran

37

Tarafından geçmek constdaha da hızlı referans. Saklamanız gerekiyorsa, bazı kaplarda, ref. sayım kopyalama işlemi tarafından otomatik olarak artırılacaktır.


4
Downvote, herhangi bir sayı olmadan görüşünü destekliyor.
kwesolowski

22

Bir keresinde, aşağıdaki kodu koştum fooalarak shared_ptrtarafından const&tekrar birlikte fooalarak shared_ptrdeğerine göre.

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

Intel core 2 dörtlü (2.4GHz) işlemcimde VS2015, x86 yayın derlemesi kullanma

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

Değere göre kopyalama sürümü, daha yavaş bir büyüklük sırasıydı.
Geçerli evre ile eşzamanlı olarak bir işlev arıyorsanız, const&sürümü tercih edin .


1
Hangi derleyici, platform ve optimizasyon ayarlarını kullandığınızı söyleyebilir misiniz?
Carlton

Vs2015 hata ayıklama derlemesini kullandım, şimdi sürüm derlemesini kullanmak için cevabı güncelledim.
tcb

1
Optimizasyon açıldığında, her ikisiyle de aynı sonuçları elde edip etmediğinizi merak ediyorum
Elliot Woods

2
Optimizasyon pek yardımcı olmuyor. sorun, kopyadaki referans sayısında kilit çekişmesidir.
Alex

1
Konu o değil. Böyle bir foo()işlev, ilk başta paylaşılan bir işaretçiyi bile kabul etmemelidir, çünkü bu nesneyi kullanmaz: çağrıyı yapan int&ve yapmayı kabul etmelidir . Bir işlev, onunla bir şeyler yapması gerektiğinde akıllı bir işaretçi nesnesini kabul eder ve çoğu zaman yapmanız gereken onu ( ) başka bir yere taşımaktır, bu nedenle bir by-value parametresinin maliyeti yoktur. p = ++x;foo(*p);main()std::move()
eepp

15

C ++ 11 beri const üzerinde değer ve düşündüğünüzden daha sık almalısınız .

Std :: shared_ptr (altta yatan T türü yerine) alıyorsanız, bununla bir şey yapmak istediğiniz için bunu yapıyorsunuz.

Bir yere kopyalamak isterseniz , kopya ile almak daha mantıklıdır ve std :: 'yi const ile alıp daha sonra kopyalamak yerine dahili olarak taşıyın. Bunun nedeni, çağıranın fonksiyonunuzu çağırırken std :: shared_ptr öğesini taşıma seçeneğine izin vermenizdir, böylece kendinize bir dizi arttırma ve azaltma işlemi kaydedersiniz. Ya da değil. Yani, işlevin arayanı işlevi çağırdıktan sonra std :: shared_ptr'e ihtiyaç duyup duymadığına ve hareket edip etmemesine bağlı olarak karar verebilir. Eğer cons & 'ile geçerseniz bu gerçekleştirilemez ve dolayısıyla tercihen değere göre alınır.

Tabii ki, arayan her ikisi de onun paylaşılan_ptr'sini daha uzun süre ihtiyaç duyarsa (böylece std :: hareket edemez) ve işlevde düz bir kopya oluşturmak istemezseniz (zayıf bir işaretçi istediğinizi veya yalnızca bazen istediğinizi) kopyalamak için, bazı koşullara bağlı olarak), bir const hala tercih edilebilir.

Örneğin, yapmalısın

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

bitmiş

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

Çünkü bu durumda daima dahili olarak bir kopya oluşturursunuz.


1

Atomik artış ve azalışın olduğu paylaşılan_kopya kopyalama işleminin zaman maliyetini bilmeden, çok daha yüksek CPU kullanım sorunu yaşadım. Atomik artışın ve azalmanın bu kadar pahalıya mal olmasını beklemiyordum.

Test sonucumu takiben int32 atomik artış ve azalış, atomik olmayan artış ve azalıştan 2 veya 40 kat daha fazla zaman alır. Windows 8.1 ile 3GHz Core i7'de aldım. İlk sonuç çekişme olmadığında, ikincisi yüksek çekişme olasılığı ortaya çıktığında ortaya çıkar. Atomik işlemlerin sonunda donanım tabanlı kilit olduğunu aklımda tutuyorum. Kilit kilittir. Çekişme gerçekleştiğinde performans kötü.

Bunu deneyimleyerek, her zaman byval'dan (shared_ptr) byref (const shared_ptr &) kullanıyorum.


1

Yeni bir blog yazısı vardı: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce

Bunun cevabı şudur: (neredeyse) asla geçmeyin const shared_ptr<T>&.
Bunun yerine temel sınıfı geçin.

Temel olarak sadece makul parametre türleri şunlardır:

  • shared_ptr<T> - Değiştirme ve sahiplik alma
  • shared_ptr<const T> - Değiştirme, sahiplik alma
  • T& - Değiştir, sahiplik yok
  • const T& - Değişiklik yapma, sahiplik yok
  • T - Değiştirmeyin, sahiplik yok, Kopyalamak için ucuz

@Accel'in belirttiği gibi https://stackoverflow.com/a/26197326/1930508 Herb Sutter'ın tavsiyesi:

Const shared_ptr & parametresini yalnızca bir kopya alıp almayacağınızdan ve sahipliğini paylaşıp paylaşmayacağınızdan emin değilseniz parametre olarak kullanın

Ancak kaç durumda emin değilsiniz? Bu nadir bir durum


0

Shared_ptr değerini değere göre geçirmenin bir maliyeti olduğu ve mümkünse kaçınılması gerektiği bilinmektedir.

Shared_ptr'den geçmenin maliyeti

Çoğu zaman paylaşılan_ptr değerini başvuru ile ve hatta const referansı ile daha iyi geçerdi.

Cpp çekirdek kılavuzunun shared_ptr iletimi için belirli bir kuralı vardır

R.34: Bir işlevin parça sahibi olduğunu ifade etmek için paylaşılan_ptr parametresini alın

void share(shared_ptr<widget>);            // share -- "will" retain refcount

Paylaşılan_ptr değerini değere göre geçirmenin gerçekten gerekli bir örneği, çağıranın paylaşılan bir nesneyi eşzamansız bir çembere geçirmesi - yani çağıran işi tamamlamadan çağıranın kapsam dışına çıkmasıdır. Callee değere göre bir share_ptr değeri alarak paylaşılan nesnenin ömrünü "uzatmalıdır". Bu durumda, paylaşılan_ptr dosyasına bir başvuru iletmek işe yaramaz.

Aynı şey, paylaşılan bir nesneyi iş parçacığına geçirmek için de geçerlidir.


-4

shared_ptr yeterince büyük değil, aynı zamanda yapıcı \ destructor, kopyadan referansa veya kopya performansa göre geçişi önemsemek için kopyadan yeterli ek yükü oluşturmak için yeterli iş yapmıyor.


15
Ölçtün mü?
curiousguy

2
@stonemetal: Yeni paylaşılan_ptr oluştururken atom talimatları nasıl olur?
Quarra

POD olmayan bir tiptir, bu nedenle çoğu ABI'de "değere göre" iletmek bile bir işaretçi iletir. Asıl sorun baytların asıl kopyalanması değil. Asm çıkışında görebileceğiniz gibi, bir shared_ptr<int>by değerini iletmek 100 x86 yönerge alır ( lockref sayısını atomik olarak artırmak / azaltmak için pahalı ed talimatları dahil ). Sabit ref ile geçmek, herhangi bir şeye bir işaretçi geçirmekle aynıdır (ve bu örnekte Godbolt derleyici gezgininde kuyruk çağrısı optimizasyonu bunu bir çağrı yerine basit bir jmp'ye dönüştürür : godbolt.org/g/TazMBU ).
Peter Cordes

TL: DR: Bu, kopya oluşturucuların bayt kopyalamaktan çok daha fazla iş yapabileceği C ++. Bu cevap tamamen çöp.
Peter Cordes

2
stackoverflow.com/questions/3628081/shared-ptr-horrible-speed Örnek olarak değere göre pass değerine göre geçen paylaşılan işaretçiler yaklaşık% 33'lük bir çalışma süresi farkı görür. Kritik performans kodu üzerinde çalışıyorsanız, çıplak işaretçiler size daha büyük bir performans artışı sağlar. Eğer hatırlıyorsanız const ref ile emin geçmek ama eğer sen değil büyük bir anlaşma değil. İhtiyacınız yoksa shared_ptr kullanmamanız çok daha önemlidir.
stonemetal
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.