Bu soruyu son dört yıldır biraz düşündüm. Ben en çok açıklamalar bu sonuca gelmiş push_back
vs emplace_back
tam resmini özledim.
Geçen yıl, C ++ 14'te C ++ Now on Type Deduction'da sunum yaptım . 13 : 49'a push_back
karşı 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_back
Veya 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_back
sonra taşındı alacak geçici bir nesne, inşa edecek v
oysa emplace_back
boyunca 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_back
olanın herhangi bir yapıcı türünü çağırmasıdır, oysa daha ihtiyatlı olan push_back
yalnızca örtük kurucuları çağırır. Örtük kurucuların güvenli olması gerekiyordu. Eğer dolaylı U
olarak bir a'dan inşa edebiliyorsanız T
, U
tüm bilgileri T
kayıpsız tutabileceğinizi söylüyorsunuz . Hemen hemen her durumda güvenlidir T
ve bunu yapmak hiç kimse umursamaz U
. Örtülü bir yapıcı iyi bir örnek, dönüşüm std::uint32_t
için std::uint64_t
. Örtülü bir dönüşüm bir kötü bir örnektir double
iç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_back
sadece iyi olmayan bir sahibi işaretçi derler geçen açık kurucular çağırabilir. Ancak, v
kapsam dışına çıktığında , yıkıcı yalnızca bir yığın nesnesi olduğu için ayrılmayan delete
bu 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_back
için emplace_back
.
Bunun yerine kodu daha güvenli kullanarak bırakmış push_back
olsaydı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.