C ++ 'da değere göre geçmek veya sürekli referansla geçmek daha mı iyi?


Yanıtlar:


203

Genel olarak en iyi uygulama tavsiye edilebilir kullanılan 1 için için Const ref kullanımı geçişte her türlü türlü (yerleşik haricinde char, int, doubleyineleyicileri için, vs.) ve fonksiyon nesneler için (elde edilen lambda'lar, sınıflar std::*_function).

Bu, özellikle hareket semantiğinin varlığından önce doğruydu . Nedeni basit: Eğer değere göre geçtiyseniz, nesnenin bir kopyasının yapılması gerekiyordu ve çok küçük nesneler dışında, bu her zaman bir referans geçirmekten daha pahalıdır.

C ++ 11 ile hareket semantiği kazandık . Özetle, hareket semantiği, bazı durumlarda, bir nesnenin kopyalanmadan “değere göre” geçirilmesine izin verir. Özellikle, bu geçirmeden o nesne bir olan durumdur rvalue .

Kendi başına, bir nesneyi hareket ettirmek hala en azından referans yoluyla geçmek kadar pahalıdır. Bununla birlikte, birçok durumda bir işlev zaten bir nesneyi dahili olarak kopyalar - yani argümanın sahipliğini alır . 2

Bu durumlarda aşağıdaki (basitleştirilmiş) ödünleşime sahibiz:

  1. Nesneyi referans olarak geçebilir, sonra dahili olarak kopyalayabiliriz.
  2. Nesneyi değere göre aktarabiliriz.

"Değere göre aktar", nesne bir değer olmadıkça nesnenin kopyalanmasına neden olur. Bir rvalue durumunda, nesne hareket ettirilebilir, böylece ikinci durum aniden artık “kopyala, sonra taşı” değil, “taşı, sonra (muhtemelen) tekrar hareket et” olur.

Uygun hareket yapıcılarını (vektörler, dizeler… gibi) uygulayan büyük nesneler için, ikinci durum, birincisinden çok daha verimlidir. Bu nedenle, işlev bağımsız değişkenin sahipliğini alırsa ve nesne türü verimli taşımayı destekliyorsa, değere göre pass değeri kullanılması önerilir .


Tarihsel bir not:

Aslında, herhangi bir modern derleyici, değerden geçerken pahalı olduğunu anlayabilmeli ve mümkünse bir const ref kullanmak için çağrıyı dolaylı olarak dönüştürebilmelidir.

Teoride. Pratikte, derleyiciler işlevin ikili arayüzünü bozmadan bunu her zaman değiştiremez. Bazı özel durumlarda (işlev satır içine alındığında), derleyici orijinal nesnenin işlevdeki eylemler yoluyla değiştirilmeyeceğini anlayabiliyorsa, kopya gerçekten kaldırılacaktır.

Ancak genel olarak derleyici bunu belirleyemez ve C ++ 'da hareket semantiğinin ortaya çıkışı bu optimizasyonu çok daha az alakalı hale getirdi.


1 Örn. Scott Meyers, Etkili C ++ .

2 Bu özellikle argüman alıp bunları dahili olarak inşa edilen nesnenin durumunun bir parçası olarak saklayabilen nesne yapıcılar için geçerlidir.


hmmm ... ref ile geçmeye değdiğinden emin değilim. double-s
sergtk

3
Her zamanki gibi, destek burada yardımcı olur. boost.org/doc/libs/1_37_0/libs/utility/call_traits.htm , bir tür yerleşik bir tür olduğunda otomatik olarak anlayacak şablon öğelerine sahiptir (bazen bunu kolayca bilemeyeceğiniz şablonlar için yararlıdır).
CesarB

13
Bu cevap önemli bir noktayı kaçırıyor. Dilimlemeyi önlemek için referans ile geçmeniz gerekir (const veya başka türlü). Bkz. Stackoverflow.com/questions/274626/…
ChrisN

6
@Chris: doğru. Polimorfizmin bütün kısmını dışarıda bıraktım çünkü bu tamamen farklı bir anlambilim. Bence OP'nin (anlamsal olarak) “değere göre” argümanı geçtiğini düşünüyorum. Diğer anlambilim gerektiğinde, soru bile kendini göstermez.
Konrad Rudolph

98

Edit: cpp-next Dave Abrahams tarafından yeni makale:

Hız ister misiniz? Değere göre geçin.


Kopyalamanın ucuz olduğu yapılar için değere göre geçiş, derleyicinin nesnelerin diğer adı olmadığını (aynı nesneler olmadığını) varsayabileceği ek bir avantaja sahiptir. Derleme referans kullanarak derleyici her zaman bunu kabul edemez. Basit örnek:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

derleyici bunu optimize edebilir

g.i = 15;
f->i = 2;

çünkü f ve g'nin aynı yeri paylaşmadığını biliyor. g bir referans olsaydı (foo &), derleyici bunu kabul edemezdi. çünkü gi f-> i ile taklit edilebilir ve 7 değerine sahip olmalıdır.

Daha pratik kurallar için, Taşıyıcıları Taşı makalesinde (şiddetle tavsiye edilen okuma) bulunan iyi bir kurallar dizisi bulunmaktadır .

  • İşlev, bağımsız değişkeni bir yan etki olarak değiştirmek istiyorsa, bunu const olmayan başvuru ile alın.
  • İşlev bağımsız değişkenini değiştirmezse ve bağımsız değişken ilkel türdeyse, onu değere göre alın.
  • Aksi takdirde, aşağıdaki durumlar dışında const referansı ile alın
    • Eğer işlevin yine de const referansının bir kopyasını alması gerekirse, değere göre alınız.

Yukarıdaki "ilkel", temel olarak birkaç bayt uzunluğunda ve polimorfik olmayan (yineleyiciler, işlev nesneleri vb.) Veya kopyalanması pahalı olmayan küçük veri türleri anlamına gelir. Bu makalede başka bir kural daha var. Fikir, bazen bir kopya yapmak ister (argümanın değiştirilememesi durumunda) ve bazen de istemez (eğer argüman zaten geçici ise, argümanın işlevinde kullanmak istemesi durumunda) , Örneğin). Makalede bunun nasıl yapılabileceği ayrıntılı olarak açıklanmaktadır. C ++ 1x'te bu teknik dil desteği ile doğal olarak kullanılabilir. O zamana kadar yukarıdaki kurallara uymalıydım.

Örnekler: Bir dize büyük harfli yapmak ve büyük harfli sürümü döndürmek için, her zaman değere göre geçilmelidir: Birinin yine de bir kopyasını alması gerekir (biri const referansını doğrudan değiştiremez) - bu yüzden mümkün olduğunca şeffaf hale getirin arayanı ve bu kopyayı erken yapın, böylece arayan mümkün olduğunca optimize edebilir - bu makalede ayrıntılı olarak açıklanmıştır:

my::string uppercase(my::string s) { /* change s and return it */ }

Ancak, parametreyi yine de değiştirmeniz gerekmiyorsa, const adresine başvurarak parametreyi alın:

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

Ancak, parametrenin amacı bağımsız değişkene bir şeyler yazmaksa, bunu const olmayan başvuru ile iletin

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}

i kurallarınızı iyi buldum ama im bir ref geçen gibi hızlandırmak hakkında konuşmak değil hakkında konuşmak ilk bölümü hakkında emin değilim. evet emin, ama ref sadece bir iyimserlik cus olarak bir şey geçmek hiç mantıklı değil. iletmekte olduğunuz yığın nesnesini değiştirmek istiyorsanız, bunu ref ile yapın. Eğer yapmazsanız, değere göre iletin. eğer değiştirmek istemiyorsanız, const-ref olarak iletin. ref olarak geçerken başka şeyler kazandığınızdan, pass-by-value ile gelen optimizasyonun önemi yoktur. anlamıyorum "hız istiyorum?" Eğer bu op gerçekleştirecek nerede sice zaten değer tarafından geçerdi ..
chikuba

Johannes: Okuduğumda bu makaleye bayıldım , ama denediğimde hayal kırıklığına uğradım. Bu kod hem GCC'de hem de MSVC'de başarısız oldu. Bir şey mi kaçırdım yoksa pratikte çalışmıyor mu?
user541686

Yine de bir kopya oluşturmak isterseniz, değere göre (const ref yerine) geçirip sonra taşıyacağımı kabul ettiğimi sanmıyorum. Bu şekilde bakın, daha verimli olan nedir, bir kopya ve bir hamle (ileri doğru geçerseniz 2 kopya bile olabilir) veya sadece bir kopya mı? Evet, her iki taraf için de bazı özel durumlar vardır, ancak verileriniz yine de taşınamazsa (örn: tonlarca tamsayı olan bir POD), ekstra kopyalara gerek yoktur.
Ion Todirel

2
Mehrdad, beklediğinizden emin değilim, ancak kod beklendiği gibi çalışıyor
Ion Todirel

Sadece derleyiciyi türdeki bir eksiklikle örtüşmediğine ikna etmek için kopyalamanın gerekliliğini düşünürdüm. __restrict__Aşırı kopyalar yerine (referanslar üzerinde de çalışabilir) GCC kullanmak istiyorum . Çok kötü standart C ++, C99'un restrictanahtar kelimesini benimsemedi .
Ruslan

12

Türüne bağlıdır. Bir referans ve kayıttan ayrılma zorunluluğu eklersiniz. Boyutu varsayılan kopyalayıcıyı kullanan işaretçilerden eşit veya daha küçük olan türlerde, değere göre geçmek daha hızlı olur.


Yerel olmayan türler için (derleyicinin kodu ne kadar iyi optimize ettiğine bağlı olarak) yalnızca referanslar yerine const referanslarını kullanarak performans artışı alabilirsiniz.
OJ.

9

Belirtildiği gibi, türüne bağlıdır. Yerleşik veri türleri için, değere göre geçmek en iyisidir. Bir çift ints gibi bazı çok küçük yapılar bile değerden geçerek daha iyi performans gösterebilir.

İşte bir örnek, bir tamsayı değerine sahip olduğunuzu ve bunu başka bir rutine aktarmak istediğinizi varsayalım. Bu değer bir kayıt defterinde saklanmak üzere en iyi duruma getirildiyse, o zaman referans olması için geçmek istiyorsanız, önce belleğe, ardından çağrıyı gerçekleştirmek için yığına yerleştirilen belleğe bir işaretçi kaydedilmelidir. Eğer değere göre geçiliyorsa, tüm gerekli olan yığına itilmiş olan kayıttır. (Ayrıntılar, farklı çağrı sistemleri ve CPU'lardan biraz daha karmaşıktır).

Eğer şablon programlama yapıyorsanız, genellikle geçilen türleri bilmediğiniz için const ref ile her zaman geçmek zorunda kalırsınız. Değere göre kötü bir şey iletmek için verilen cezalar yerleşik bir tipin geçilmesinden çok daha kötüdür. const ref tarafından.


Terminoloji hakkında not: Bir milyon int içeren bir yapı hala bir "POD tipi" dir. Muhtemelen 'yerleşik tipler için değere göre geçmek en iyisidir' demek istersiniz.
Steve Jessop

6

Şablon olmayan bir işlevin arabirimini tasarlarken normalde çalıştığım şey budur:

  1. İşlev parametreyi değiştirmek istemiyorsa ve değerin kopyalanması ucuzsa (int, double, float, char, bool vb.) Değere göre geçin. Std :: string, std :: vector ve geri kalanının Standart kitaplıktaki kapların DEĞİLDİR)

  2. Değerin kopyalanması pahalıysa ve işlev, işaret edilen değeri değiştirmek istemiyorsa ve NULL, işlevin işlediği bir değerse const pointer ile geçin.

  3. Değerin kopyalanması pahalıysa ve işlev işaret edilen değeri değiştirmek istiyorsa ve NULL, işlevin işlediği bir değerse const olmayan işaretçi tarafından iletilir.

  4. Değerin kopyalanması pahalı olduğunda ve işlev belirtilen değeri değiştirmek istemediğinde const referansı ile geçin ve bunun yerine bir işaretçi kullanılmışsa NULL geçerli bir değer olmaz.

  5. Değerin kopyalanması pahalı olduğunda ve işlev, belirtilen bir değeri değiştirmek istediğinde ve bunun yerine bir işaretçi kullanılmışsa NULL geçerli bir değer olmazsa const olmayan başvuruyla iletin.


std::optionalResme ekleyin ve artık işaretçilere ihtiyacınız yok.
Violet Zürafa

5

Sanki cevabını almışsın. Değere göre geçmek pahalıdır, ancak ihtiyacınız olduğunda çalışmak için bir kopyasını verir.


Bunun neden seçildiğinden emin değilim? Bana mantıklı geliyor. Şu anda saklanan değere ihtiyacınız olacaksa, değere göre geçin. Değilse, referansı iletin.
Totty

4
Tamamen türe bağlıdır. Referans olarak bir POD (düz eski veri) türü yapmak aslında daha fazla bellek erişimine neden olarak performansı düşürebilir.
Torlack

1
Açıkçası int referans ile geçmek hiçbir şey tasarruf etmez! Bence soru bir göstergeden daha büyük şeyleri ifade ediyor.
GeekyMonkey

4
O kadar açık değil, bilgisayarların const ref ile basit şeyleri geçerek nasıl çalıştığını gerçekten anlamayan insanlar tarafından bir sürü kod gördüm, çünkü bunun en iyi şey olduğu söylendi.
Torlack

4

Const referansı ile geçen bir kural olarak daha iyidir. Ancak işlev argümanını yerel olarak değiştirmeniz gerekirse, değere göre iletmeyi daha iyi kullanmalısınız. Bazı temel tipler için genel olarak performans hem değere göre hem de referans olarak aynıdır. Aslında işaretçi tarafından dahili olarak temsil edilen referans, bu nedenle örneğin işaretçi için her iki geçişin de performans açısından aynı olmasını veya değere göre geçişin gereksiz dereference nedeniyle daha hızlı olabileceğini bekleyebilirsiniz.


Parametrenin callee'nin kopyasını değiştirmeniz gerekirse, değere göre geçmek yerine çağrılan kodda bir kopya oluşturabilirsiniz. IMO genellikle aşağıdaki gibi bir uygulama ayrıntısına dayalı olarak API'yi seçmemelisiniz: çağıran kodun kaynağı her iki şekilde aynıdır, ancak nesne kodu değildir.
Steve Jessop

Eğer değeri geçerseniz kopya oluşturulur. Ve IMO, hangi şekilde bir kopya oluşturduğunuzun bir önemi yoktur: değer veya yerel olarak geçen argüman aracılığıyla - C ++ ile ilgili olan budur. Ancak tasarım açısından size katılıyorum. Ama burada sadece C ++ özelliklerini tanımladım ve tasarıma dokunmuyorum.
sergtk

1

Genel bir kural olarak, sınıf olmayan türler için değer ve sınıflar için const referansı. Bir sınıf gerçekten küçükse, muhtemelen değere göre geçmek daha iyidir, ancak fark minimumdur. Gerçekten kaçınmak istediğiniz şey, değere göre devasa bir sınıf geçirmek ve çoğaltmaktır - bu, içinde birkaç element bulunan bir std :: vektörü geçiriyorsanız büyük bir fark yaratacaktır.


Anladığım kadarıyla, std::vectoraslında öğelerini yığın üzerine ayırır ve vektör nesnesinin kendisi asla büyümez. Bekle. İşlem, vektörün bir kopyasının yapılmasına neden olursa, aslında tüm öğeleri kopyalar ve çoğaltır. Kötü olur.
Steven Lu

1
Evet, ben de öyle düşünüyordum. sizeof(std::vector<int>)sabittir, ancak değere göre iletilmesi derleyici zekası olmadan içeriği kopyalamaya devam eder.
Peter

1

Küçük tipler için değere göre geçiş.

Büyük türler için const referanslarıyla geçin (big'in tanımı makineler arasında değişebilir) AMA, C ++ 11'de, veriyi tüketecekseniz, değere göre geçin, çünkü hareket semantiğini kullanabilirsiniz. Örneğin:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

Şimdi arama kodu:

Person p(std::string("Albert"));

Ve sadece bir nesne yaratılacak ve doğrudan name_sınıftaki üyeye taşınacaktı Person. Eğer const referansı ile geçerseniz, onu içine koymak için bir kopyasının yapılması gerekecektir name_.


-5

Basit fark: - Fonksiyonda giriş ve çıkış parametrelerine sahibiz, bu yüzden geçen giriş ve çıkış parametreniz aynıysa, giriş ve çıkış parametresi farklıysa, başka bir referans ile çağrı kullanın, sonra değere göre çağrı kullanmak daha iyidir.

misal void amount(int account , int deposit , int total )

giriş parametresi: hesap, depozito çıkış parametresi: toplam

giriş ve çıkış farklı kullanım çağrı tonoz

  1. void amount(int total , int deposit )

girdi toplam mevduat çıktı toplam

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.