Geçiş değeri C ++ 11 için makul bir varsayılan mı?


142

Geleneksel C ++ 'da, değere göre işlevlere ve yöntemlere geçmek büyük nesneler için yavaştır ve genellikle kaşlarını çatır. Bunun yerine, C ++ programcıları referansları aktarma eğilimindedir, bu da daha hızlıdır, ancak sahiplik ve özellikle bellek yönetimi hakkında her türlü karmaşık soruyu ortaya çıkarır (nesnenin yığın halinde ayrılması durumunda)

Şimdi, C ++ 11'de Rvalue referanslarımız ve hareket yapıcılarımız var, bu std::vectorda bir fonksiyonun değerine ve fonksiyonunun dışına geçmek için ucuz olan büyük bir nesneyi (an gibi ) uygulamak anlamına geliyor .

Öyleyse, bu, varsayılan gibi std::vectorve gibi tür örnekleri için değere göre geçilmesi gerektiği anlamına std::stringmı geliyor? Özel nesneler ne olacak? Yeni en iyi uygulama nedir?


22
pass by reference ... which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated). Mülkiyet için ne kadar karmaşık veya sorunlu olduğunu anlamıyorum? Bir şey kaçırmış olabilir miyim?
iammilind

1
@iammilind: Kişisel deneyimlerden bir örnek. Bir iş parçacığının bir dize nesnesi vardır. Başka bir iş parçacığını üreten bir işleve geçirilir, ancak arayan tarafından bilinmeyen işlev, dizeyi const std::string&bir kopya olarak değil aldı . İlk iş parçacığı daha sonra ...
Zan Lynx

12
@ZanLynx: Bu, hiçbir zaman bir iş parçacığı işlevi olarak adlandırılmak üzere açıkça tasarlanmamış bir işleve benziyor.
Nicol Bolas

5
İammilind ile hemfikir olduğum için herhangi bir sorun görmüyorum. Sabit başvuruyla iletme, "büyük" nesneler için varsayılan, küçük nesneler için de değer olmalıdır. Büyük ve küçük arasındaki sınırı yaklaşık 16 baytta (veya 32 bit sistemde 4 işaretçi) koyardım.
JN

3
Herb Sutter Temellere Geri Dönüyor! CppCon'daki Modern C ++ sunumunun temelleri bu konuda biraz ayrıntıya girdi. Burada video .
Chris Drew

Yanıtlar:


138

Makul bir varsayılan var ise sen vücudun içinde bir kopyasını yapmak gerekir. Dave Abrahams'ın savunduğu şey bu :

Yönerge: İşlev argümanlarınızı kopyalamayın. Bunun yerine, bunları değere göre iletin ve derleyicinin kopyalamayı yapmasına izin verin.

Kodda bu, bunu yapma anlamına gelir:

void foo(T const& t)
{
    auto copy = t;
    // ...
}

ama bunu yapın:

void foo(T t)
{
    // ...
}

arayanın şu şekilde kullanabileceği bir avantaja sahiptir foo:

T lval;
foo(lval); // copy from lvalue
foo(T {}); // (potential) move from prvalue
foo(std::move(lval)); // (potential) move from xvalue

ve çok az iş yapılır. Aynı işlemleri referanslarla yapmak için iki aşırı yüklemeye ihtiyacınız vardır void foo(T const&);ve void foo(T&&);.

Bunu göz önünde bulundurarak, şimdi değerli kurucularımı şöyle yazdım:

class T {
    U u;
    V v;
public:
    T(U u, V v)
        : u(std::move(u))
        , v(std::move(v))
    {}
};

Aksi takdirde, consthala referans olarak geçilmesi makuldür.


29
+1, özellikle son bit için :) Hareket Oluşturucuların yalnızca hareket ettirilecek nesnenin daha sonra değişmesi beklenmiyorsa çağrılabileceğini unutmamalıyız: SomeProperty p; for (auto x: vec) { x.foo(p); }örneğin uymuyor. Ayrıca, Move Constructors'ın bir bedeli vardır (nesne ne kadar büyükse, maliyet de o kadar yüksektir) const&ve esasen ücretsizdir.
Matthieu M.Eyl

25
@MatthieuM. Ancak "nesne ne kadar büyükse, maliyetin de o kadar yüksek" olmasının aslında ne anlama geldiğini bilmek önemlidir: "büyük" aslında "sahip olduğu daha fazla üye değişkeni" anlamına gelir. Örneğin, std::vectorbir milyon öğeye sahip bir öğeyi taşımak, beş öğeli öğeyi taşımakla aynıdır , çünkü yığıntaki diziye yalnızca işaretçi hareket eder, vektördeki her nesne değil. Yani aslında o kadar büyük bir mesele değil.
Lucas

+1 Ayrıca, C ++ 11 kullanmaya başladığımdan beri, by-pass-by-move yapısını kullanma eğilimindeyim. Bu benim kod şimdi her std::moveyerde olduğundan beri beni biraz huzursuz hissettiriyor ..
stijn

1
Bir risk var const&, bu beni birkaç kez tetikledi. void foo(const T&); int main() { S s; foo(s); }. Türleri farklı olsa da, S'yi argüman olarak alan bir T yapıcısı varsa, bu derlenebilir. Bu yavaş olabilir, çünkü büyük bir T nesnesi oluşturuluyor olabilir. Sen olabilir düşünmek senin kopyalamadan bir başvuru geçirmeden ama ne olabilir. Daha fazlasını istediğim bir sorunun bu cevabına bakın . Temel olarak, &genellikle sadece değerlere bağlanır, ancak bunun için bir istisna vardır rvalue. Alternatifler var.
Aaron McDaid

1
@AaronMcDaid Bu eski haber, yani C ++ 11'den önce bile her zaman farkında olmanız gereken bir şey. Ve bu konuda hiçbir şey değişmedi.
Luc Danton

71

Neredeyse tüm durumlarda, anlambiliminiz şunlardan biri olmalıdır:

bar(foo f); // want to obtain a copy of f
bar(const foo& f); // want to read f
bar(foo& f); // want to modify f

Diğer tüm imzalar sadece az miktarda ve iyi bir gerekçe ile kullanılmalıdır. Derleyici şimdi hemen hemen her zaman en verimli şekilde çalışacaktır. Sadece kodunuzu yazarak devam edebilirsiniz!


2
Her ne kadar bir argümanı değiştireceksem imleci geçmeyi tercih ederim. Google stil rehberine, bunun işlevin imzasını ( google-styleguide.googlecode.com/svn/trunk/… ) tekrar kontrol etmenize gerek kalmadan argümanın değiştirileceğini daha açık hale getirdiğini kabul ediyorum .
Max Lybbert

40
İşaretçilerden hoşlanmamamın nedeni, işlevlerime olası bir başarısızlık durumu eklemesidir. Onların provably doğru olduğunu bu yüzden büyük ölçüde içinde gizlemek için hatalar için yer azaltacağından, bu benim tüm fonksiyonlarını yazmayı deneyin. foo(bar& x) { x.a = 3; }Daha güvenilir (ve okunabilir!) Bir çok bir halt olduğunufoo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;
ayjay

22
@Max Lybbert: Bir işaretçi parametresiyle, işlevin imzasını kontrol etmenize gerek yoktur, ancak işlevin sahipliğini alacağı vb. Null işaretçiler geçirip geçmediğinizi öğrenmek için belgeleri kontrol etmeniz gerekir. IMHO, işaretçi parametresi sabit olmayan bir referanstan çok daha az bilgi iletir. Ancak, çağrı sitesinde, argümanın değiştirilebileceği ( refC # 'daki anahtar kelime gibi) görsel bir ipucu olması iyi olur .
Luc Touraille

Değere göre geçmek ve hareket semantiğine güvenmekle ilgili olarak, bu üç seçeneğin parametrenin amaçlanan kullanımını açıklamak için daha iyi bir iş yaptığını hissediyorum. Bunlar her zaman takip ettiğim yönergeler.
Trevor Hickey

1
@AaronMcDaid is shared_ptr intended to never be null? Much as (I think) unique_ptr is?Bu varsayımların ikisi de yanlıştır. unique_ptrve shared_ptrnull / nullptrdeğerleri tutabilir . Boş değerler konusunda endişelenmek istemiyorsanız, referanslar kullanmalısınız, çünkü bunlar asla boş olamaz. Ayrıca ->can sıkıcı bulduğunuz yazmak zorunda kalmayacaksınız :)
Julian

10

İşlev gövdesi içinde nesnenin bir kopyasına veya yalnızca nesneyi taşımanız gerekiyorsa parametreleri değere göre geçirin. Tarafından geçmek const&yalnızca nesneye olmayan mutasyona erişmeniz gerekiyorsa.

Nesne kopyalama örneği:

void copy_antipattern(T const& t) { // (Don't do this.)
    auto copy = t;
    t.some_mutating_function();
}

void copy_pattern(T t) { // (Do this instead.)
    t.some_mutating_function();
}

Nesne taşıma örneği:

std::vector<T> v; 

void move_antipattern(T const& t) {
    v.push_back(t); 
}

void move_pattern(T t) {
    v.push_back(std::move(t)); 
}

Değişmeyen erişim örneği:

void read_pattern(T const& t) {
    t.some_const_function();
}

Gerekçe için Dave Abrahams ve Xiang Fan'ın bu blog gönderilerine bakın .


0

Bir işlevin imzası, amaçlanan kullanımını yansıtmalıdır. Okunabilirlik, optimize edici için de önemlidir.

Bu, bir optimizasyoncunun en azından en azından en azından gerçekte olmasa da teoride en hızlı kodu yaratması için en iyi önkoşuldur.

Performans değerlendirmeleri genellikle parametre geçirme bağlamında abartılır. Mükemmel yönlendirme buna bir örnektir. Gibi fonksiyonlar emplace_backçoğunlukla çok kısa ve eğiktir.

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.