Neden emplace_back yerine push_back kullanmalıyım?


232

C ++ 11 vektörleri yeni işleve sahiptir emplace_back. push_backKopyalardan kaçınmak için derleyici optimizasyonlarından farklı olarak , emplace_backyerinde bir nesne oluşturmak için bağımsız değişkenleri doğrudan yapıcıya göndermek için mükemmel yönlendirme kullanır. Bana öyle emplace_backgeliyor ki her şey push_backyapabiliyor, ama bazen bunu daha iyi yapacak (ama asla kötü olmayacak).

Hangi sebebi kullanmam gerekiyor push_back?

Yanıtlar:


162

Bu soruyu son dört yıldır biraz düşündüm. Ben en çok açıklamalar bu sonuca gelmiş push_backvs emplace_backtam resmini özledim.

Geçen yıl, C ++ 14'te C ++ Now on Type Deduction'da sunum yaptım . 13 : 49'a push_backkarşı konuşmaya başlıyorum emplace_back, ancak bundan önce destekleyici kanıtlar sağlayan yararlı bilgiler var.

Gerçek birincil fark, örtük ve açık kurucularla ilgilidir. push_backVeya iletmek istediğimiz tek bir argümanın olduğu durumu düşünün emplace_back.

std::vector<T> v;
v.push_back(x);
v.emplace_back(x);

Optimizasyon derleyiciniz bu konuyu ele geçirdikten sonra, üretilen kod açısından bu iki ifade arasında fark yoktur. Geleneksel bilgelik olduğunu push_backsonra taşındı alacak geçici bir nesne, inşa edecek voysa emplace_backboyunca argüman ileri ve hiçbir kopya veya hamle ile yerinde doğrudan inşa edecek. Bu, standart kitaplıklarda yazılan koda dayalı olarak doğru olabilir, ancak en iyileştirici derleyicinin işinin yazdığınız kodu oluşturmak olduğu şeklindeki yanlış varsayımı yapar. Optimizasyon derleyicisinin işi aslında, platforma özgü optimizasyonlar konusunda uzman olsaydınız ve sürdürülebilirlik, sadece performans umurunda olmasaydı yazdığınız kodu üretmektir.

Bu iki ifade arasındaki asıl fark, daha güçlü emplace_backolanın herhangi bir yapıcı türünü çağırmasıdır, oysa daha ihtiyatlı olan push_backyalnızca örtük kurucuları çağırır. Örtük kurucuların güvenli olması gerekiyordu. Eğer dolaylı Uolarak bir a'dan inşa edebiliyorsanız T, Utüm bilgileri Tkayıpsız tutabileceğinizi söylüyorsunuz . Hemen hemen her durumda güvenlidir Tve bunu yapmak hiç kimse umursamaz U. Örtülü bir yapıcı iyi bir örnek, dönüşüm std::uint32_tiçin std::uint64_t. Örtülü bir dönüşüm bir kötü bir örnektir doubleiçin std::uint8_t.

Programlamamızda temkinli olmak istiyoruz. Güçlü özellikleri kullanmak istemiyoruz çünkü özellik ne kadar güçlü olursa, yanlışlıkla yanlış veya beklenmedik bir şey yapmak o kadar kolay olur. Açık kurucuları çağırmak istiyorsanız, gücüne ihtiyacınız var emplace_back. Sadece örtük kurucuları çağırmak istiyorsanız, 'in güvenliğine sadık kalın push_back.

Bir örnek

std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile

std::unique_ptr<T>adlı kullanıcının açık bir kurucusu var T *. Çünkü emplace_backsadece iyi olmayan bir sahibi işaretçi derler geçen açık kurucular çağırabilir. Ancak, vkapsam dışına çıktığında , yıkıcı yalnızca bir yığın nesnesi olduğu için ayrılmayan deletebu işaretçiyi çağırmaya çalışır new. Bu, tanımlanmamış davranışa yol açar.

Bu sadece icat edilmiş bir kod değildir. Bu, karşılaştığım gerçek bir üretim hatasıydı. Kod vardı std::vector<T *>, ancak içeriğe sahipti. C ++ 11'e geçişin bir parçası olarak, vektörün belleğine sahip olduğunu belirtmek T *için doğru şekilde değiştirdim std::unique_ptr<T>. Ancak, ben de değişti bu yüzden, "emplace_back her şey push_back yapabileceği gelmez ve daha, neden şimdiye kadar push_back kullanırsınız?" Düşünce sırasında, 2012 yılında benim anlayışıma kapalı bu değişiklikleri dayandırarak push_backiçin emplace_back.

Bunun yerine kodu daha güvenli kullanarak bırakmış push_backolsaydım, anında bu uzun süredir devam eden hatayı yakalardım ve C ++ 11'e yükseltme başarısı olarak görülüyordu. Bunun yerine, hatayı maskeledim ve aylara kadar bulamadım.


Örneğinizde tam olarak ne yaptığını ve neden yanlış olduğunu ayrıntılandırabilirseniz yardımcı olacaktır.
eddi

4
@ eddi: Bunu açıklayan bir bölüm ekledim: std::unique_ptr<T>açık bir kurucu var T *. Çünkü emplace_backsadece iyi olmayan bir sahibi işaretçi derler geçen açık kurucular çağırabilir. Ancak, vkapsam dışına çıktığında , yıkıcı yalnızca bir yığın nesnesi olduğu için ayrılmayan deletebu işaretçiyi çağırmaya çalışır new. Bu, tanımlanmamış davranışa yol açar.
David Stone

Bunu gönderdiğiniz için teşekkürler. Cevabımı yazdığımda bunu bilmiyordum ama şimdi daha sonra öğrendiğimde kendim yazsaydım :) Gerçekten bulabilecekleri en hippi şeyi yapmak için yeni özelliklere geçen insanları tokatlamak istiyorum. . Çocuklar, insanlar da 11 C ++ önce C ++ ve değil herşeyi sorunlu oldu hakkında. Bir özelliği neden kullandığınızı bilmiyorsanız kullanmayın . Bunu yayınladığınız için çok mutluyum ve umarım daha fazla oy alır, bu yüzden benimkinin üzerine çıkar. +1
user541686

1
@CaptainJacksparrow: Onları kastettiğim örtük ve açık diyorum. Hangi kısmı karıştırdınız?
David Stone

4
@CaptainJacksparrow: Bir explicitkurucu, anahtar sözcüğün explicitkendisine uygulandığı bir kurucudur . "Örtük" yapıcı, o anahtar kelimeye sahip olmayan herhangi bir kurucudur. std::unique_ptr'Dan yapıcı olması durumunda T *, uygulayıcının std::unique_ptryazarı bu yapıcıyı yazmıştır, ancak buradaki mesele emplace_back, o açık yapıcı olarak adlandırılan bu türdeki kullanıcının çağırmasıdır. Öyle olsaydı push_back, bu yapıcıyı çağırmak yerine, yalnızca örtük kurucuları çağırabilecek örtük bir dönüşüme dayanırdı.
David Stone

117

push_backher zaman çok sevdiğim tek tip başlatma kullanımına izin verir. Örneğin:

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

Öte yandan, v.emplace_back({ 42, 121 });çalışmaz.


58
Bunun yalnızca toplu başlatma ve başlatıcı listesi başlatma için geçerli olduğunu unutmayın. {}Gerçek bir kurucu çağırmak için sözdizimi kullanıyorsanız , {}'s kaldırabilir ve kullanabilirsiniz emplace_back.
Nicol Bolas

Aptalca soru zamanı: emplace_back, yapı vektörleri için kullanılamaz mı? Yoksa sadece {42,121} değişmezi kullanan bu stil için değil mi?
Phil H

1
@LucDanton: Dediğim gibi, sadece toplu ve başlatıcı listesi başlatma için geçerlidir . {}Gerçek kurucuları çağırmak için sözdizimini kullanabilirsiniz . aggregate2 tamsayı alan bir kurucu verebilirsiniz ve {}sözdizimi kullanılırken bu kurucu çağrılır . Mesele şu ki , bir kurucu çağırmaya çalışıyorsanızemplace_back , kurucu yerinde çağırdığı için tercih edilir. Bu nedenle, türün kopyalanabilir olmasını gerektirmez.
Nicol Bolas

11
Bu standartta bir kusur olarak görülmüştür ve çözülmüştür. Bkz. Cplusplus.github.io/LWG/lwg-active.html#2089
David Stone

3
@DavidStone Çözülmüş olsaydı, hala "aktif" listede olmazdı ... hayır? Beklenmedik bir sorun olarak kalmaya devam ediyor. " [2018-08-23 Batavia Sorunları işleniyor] " başlıklı en son güncelleme, " P0960 (şu anda uçuşta) bunu çözmesi gerektiğini " söylüyor . Yine de emplaceaçıkça bir kazan plakası kurucusu yazmadan toplamaya çalışan kodu derleyemiyorum . Bu noktada, bir kusur olarak muamele görüp görmeyeceği ve dolayısıyla backporting için uygun olup olmayacağı veya C ++ <20 kullanıcılarının SoL olup olmayacağı da belirsizdir.
underscore_d

80

C ++ 11 öncesi derleyicileri ile geriye dönük uyumluluk.


21
Bu C ++ 'ın laneti gibi görünüyor. Her yeni sürümde tonlarca harika özellik alıyoruz, ancak birçok şirket ya uyumluluk için bazı eski sürümleri kullanmaya ya da belirli özelliklerin kullanılmasını engellemeye (izin vermiyorsa) takılıyor.
Dan Albert

6
@Mehrdad: Harika olabildiğiniz zaman neden yeteri kadar uzlaşıyorsunuz? Yeterli olsa bile , blubda programlama yapmak istemezdim. Özellikle bu örnek için böyle olduğunu söylememekle birlikte, zaman programlamasının çoğunu uyumluluk uğruna C89'da geçiren biri olarak, kesinlikle gerçek bir sorun.
Dan Albert

3
Bunun gerçekten sorunun cevabı olduğunu düşünmüyorum. Bana göre push_back, tercih edilen yerlerde kullanım çantaları istiyor .
Bay Boy

3
@ Mr.Boy: C ++ 11 öncesi derleyicilerle geriye dönük uyumlu olmak istediğinizde tercih edilir. Cevabımda bu belirsiz miydi?
user541686

6
Bu nedenle bu okuma hepiniz için, beklediğimden çok daha fazla dikkat aldı emplace_backolduğu değil bir "büyük" sürüm push_back. Potansiyel olarak tehlikeli bir versiyonu. Diğer cevapları okuyun.
user541686

68

Emplace_back'in bazı kitaplık uygulamaları, Visual Studio 2012, 2013 ve 2015 ile birlikte gelen sürüm de dahil olmak üzere C ++ standardında belirtildiği gibi davranmaz.

Bilinen derleyici hatalarını karşılamak std::vector::push_back()için parametrelerin yineleyicilere veya çağrıdan sonra geçersiz olacak diğer nesnelere başvurup başvurmadığını tercih edin.

std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers

Bir derleyicide, v, beklenen 123 ve 123 yerine 123 ve 21 değerlerini içerir. Bunun nedeni, 2. emplace_backnoktaya yapılan çağrının , hangi noktada yeniden boyutlandırma ile sonuçlandığıdır v[0].

Yukarıdaki kodun çalışan bir uygulaması aşağıdaki push_back()yerine kullanılacaktır emplace_back():

std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);

Not: Bir ints vektörünün kullanımı gösterim amaçlıdır. Dinamik olarak tahsis edilmiş üye değişkenleri ve emplace_back()zor bir çökme ile sonuçlanan çağrı içeren çok daha karmaşık bir sınıf ile bu sorunu keşfettim .


Bekle. Bu bir hata gibi görünüyor. push_backBu durumda nasıl farklı olabilir ?
balki

12
Emplace_back () çağrısı, yerinde inşaatı gerçekleştirmek için mükemmel yönlendirme kullanır ve bu nedenle v [0], vektör yeniden boyutlandırılana kadar (v [0] geçersiz) değerlendirilmez. push_back yeni elemanı oluşturur ve elemanı gerektiği şekilde kopyalar / taşır ve v [0] herhangi bir yeniden tahsis öncesinde değerlendirilir.
Marc

1
@Marc: Emplace_back'in aralık içindeki öğeler için bile çalıştığı standart tarafından garanti edilir.
David Stone

1
@DavidStone: Lütfen bu davranışın standartların neresinde garanti edildiğine dair bir referans verebilir misiniz? Her iki durumda da, Visual Studio 2012 ve 2015 yanlış davranış sergiler.
Marc

4
@cameino: gereksiz kopyalamayı azaltmak için parametresinin değerlendirilmesini geciktirmek için emplace_back bulunmaktadır. Davranış ya tanımsız ya da bir derleyici hatası (standardın incelenmesini bekliyor). Son zamanlarda Visual Studio 2015'e karşı aynı testi yaptım ve Release x64 altında 123,3, Release Win32 altında 123,40 ve Debug x64 ve Debug Win32 altında 123, -572662307 aldım.
Marc
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.