Akıllı işaretçiler (paylaşılan_tr) referans veya değere göre nasıl döndürülür?


97

Diyelim ki a döndüren bir yöntemi olan bir sınıfım var shared_ptr.

Referans veya değer ile iade etmenin olası faydaları ve dezavantajları nelerdir?

Olası iki ipucu:

  • Erken nesne imhası. shared_ptrBy (const) referansını döndürürsem, referans sayacı artırılmaz, bu nedenle nesnenin başka bir bağlamda kapsam dışına çıktığında (örneğin başka bir iş parçacığı) silinmesi riskini alırım. Bu doğru mu? Ya ortam tek iş parçacıklıysa, bu durum da olabilir mi?
  • Maliyet. Değere göre geçiş kesinlikle ücretsiz değildir. Mümkün olduğunca ondan kaçınmaya değer mi?

Herkese teşekkürler.

Yanıtlar:


115

Akıllı işaretçileri değere göre döndür.

Söylediğiniz gibi, referans olarak iade ederseniz, referans sayısını doğru bir şekilde artırmayacaksınız, bu da uygun olmayan zamanda bir şeyi silme riskini ortaya çıkarır. Bu tek başına referansla geri dönmemek için yeterli neden olmalıdır. Arayüzler sağlam olmalıdır.

Geri dönüş değeri optimizasyonu (RVO) sayesinde günümüzde maliyet endişesi tartışmalı , bu nedenle modern derleyicilerde bir artırma-artırma-azaltma dizisine veya buna benzer bir şeye maruz kalmayacaksınız. Dolayısıyla, a döndürmenin en iyi yolu shared_ptr, basitçe değere göre döndürmektir:

shared_ptr<T> Foo()
{
    return shared_ptr<T>(/* acquire something */);
};

Bu, modern C ++ derleyicileri için kesin olmayan bir RVO fırsatıdır. Tüm optimizasyonlar kapatıldığında bile Visual C ++ derleyicilerinin RVO uyguladığını biliyorum. Ve C ++ 11'in hareket semantiği ile bu endişe daha da az alakalı. (Ancak emin olmanın tek yolu profil çizmek ve deney yapmaktır.)

Hala ikna olmadıysanız, Dave Abrahams'ın değere göre geri dönüşü tartışan bir makalesi var. Burada bir pasajı yeniden oluşturuyorum; Gidip makalenin tamamını okumanızı şiddetle tavsiye ederim:

Dürüst olun: Aşağıdaki kod size nasıl hissettiriyor?

std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();

Açıkçası, daha iyi bilmem gerekse de bu beni tedirgin ediyor. Prensip olarak, ne zaman get_names() döner, elimizdeki bir kopyalama vectorait strings. Ardından, başlattığımızda onu tekrar nameskopyalamalıyız ve ilk kopyayı yok etmeliyiz. Vektörde Ns varsa string, her kopya N + 1 bellek tahsisi ve dizi içeriği kopyalandıkça önbellek dostu olmayan bir dizi veri erişimi gerektirebilir.

Bu tür bir endişeyle yüzleşmek yerine, gereksiz kopyalardan kaçınmak için sık sık referansa geri döndüm:

get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );

Ne yazık ki bu yaklaşım ideal olmaktan uzaktır.

  • Kod% 150 büyüdü
  • constBırakmak zorunda kaldık çünkü isimleri değiştiriyoruz.
  • İşlevsel programcıların bize hatırlatmayı sevdiği gibi, mutasyon, referans şeffaflığı ve eşitlik mantığını baltalayarak kodu akıl yürütmeyi daha karmaşık hale getirir.
  • Artık isimler için katı değer anlamlarına sahip değiliz.

Ancak verimlilik kazanmak için kodumuzu bu şekilde karıştırmak gerçekten gerekli mi? Neyse ki, cevabın hayır olduğu ortaya çıkıyor (ve özellikle C ++ 0x kullanıyorsanız değil).


Referans olarak geri dönmenin RVO'yu kesinlikle imkansız hale getirmesi nedeniyle RVO'nun soruyu tartışmalı hale getirdiğini söyleyebilirim.
Edward Strange

@CrazyEddie: Doğru, OP'nin değer bazında geri dönmesini önermemin nedenlerinden biri bu.
In silico

Standardın izin verdiği RVO kuralı, standart tarafından garanti edilen senkronizasyon / önceden-olur ilişkileri hakkındaki kurallara üstün geliyor mu?
edA-qa mort-ora-y

1
@ edA-qa mort-ora-y: Yan etkileri olsa bile RVO'ya açıkça izin verilir. Örneğin, cout << "Hello World!";varsayılan ve kopya yapıcıda bir Hello World!ifadeniz varsa, RVO etkin olduğunda iki s görmezsiniz . Ancak bu, düzgün tasarlanmış akıllı işaretçiler için, hatta senkronizasyon için bile sorun olmamalıdır.
In silico

23

İlgili herhangi akıllı işaretçi (sadece Shared_ptr değil), ben birine başvuru döndürmek için hiç kabul edilebilir olduğunu sanmıyorum, ben referans veya ham pointer ile hç için çok kararsız olacaktır. Neden? Çünkü daha sonra bir referansla sığ kopyalanmayacağından emin olamazsınız. İlk noktanız, bunun neden bir endişe olması gerektiğini tanımlar. Bu, tek iş parçacıklı bir ortamda bile olabilir. Programlarınıza kötü kopya semantiği koymak için verilere eşzamanlı erişime ihtiyacınız yoktur. Kullanıcılarınızın işaretçiyi bıraktıktan sonra ne yapacağını gerçekten kontrol edemezsiniz, bu nedenle API kullanıcılarınıza kendilerini asmaları için yeterli ip vererek kötüye kullanmaya teşvik etmeyin.

İkinci olarak, mümkünse akıllı işaretçinizin uygulamasına bakın. İnşaat ve yıkım ihmal edilebilir seviyeye yakın olmalıdır. Bu ek yük kabul edilebilir değilse, akıllı bir işaretçi kullanmayın! Ancak bunun ötesinde, sahip olduğunuz eşzamanlılık mimarisini de incelemeniz gerekecek, çünkü işaretçinin kullanımlarını izleyen mekanizmaya karşılıklı olarak münhasır erişim, sizi yalnızca shared_ptr nesnesinin oluşturulmasından daha fazla yavaşlatacaktır.

3 yıl sonra düzenleme: C ++ 'daki daha modern özelliklerin ortaya çıkmasıyla, cevabımı, arama işlevinin kapsamının dışında hiçbir zaman yaşamayan ve olmayan bir lambda yazdığınızda durumları daha kabul edecek şekilde değiştirirdim başka bir yere kopyalandı. Burada, paylaşılan bir işaretçiyi kopyalamanın çok az ek yükünden kurtarmak istiyorsanız, bu adil ve güvenli olacaktır. Neden? Çünkü referansın asla yanlış kullanılmayacağını garanti edebilirsiniz.

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.