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.