C++ProgramlamaKıdemli C++ Geliştirici

Neden argümanların doğrudan **std::map::emplace**'e geçirilmesi, anahtar çakışması nedeniyle ekleme reddedilse bile eşleşen değerin inşaat maliyetini artırabilir, ve **std::piecewise_construct** etiketi ile birlikte **std::forward_as_tuple** bu maliyeti nasıl ortadan kaldırır?

Hintsage yapay zeka asistanı ile mülakatları geçin

Sorunun cevabı.

std::map::emplace'i map.emplace(key, value_args...) gibi argümanlarla çağırdığınızda, C++ standardı uygulamanın geçici bir std::pair<const Key, T> (veya onun düğüm eşdeğeri) oluşturmasını gerektirir, önce anahtarın benzersizliğini kontrol etmeden. Eğer anahtar zaten varsa, bu düğüm hemen atılır, yani eşleşen değer T'nin herhangi bir pahalı inşası boşa gitmiş olur.

std::piecewise_construct etiketi, bu davranışı değiştirerek konteynıra sonraki iki demet argümanını, sırasıyla anahtar ve değer yapıcıları için argüman listeleri olarak değerlendirmesi için işaret eder. Yapıcı argümanları std::forward_as_tuple ile sarmalayarak, konteyner eşleşen değerin gerçek örneğini yalnızca yeni tahsis edilen düğüm içinde, ve yalnızca anahtarın benzersiz olduğu onaylanırsa başlatılmasını erteler. Bu, değerin tam olarak bir kez, nihai bellek konumunda inşa edilmesini ve ekleme başarısız olursa asla inşa edilmemesini garanti eder.

Hayattan bir durum

Yüksek frekanslı bir ticaret platformunda, std::map<OrderID, Order> içinde serileştirilmiş Order nesnelerini (vektörler ve dizeler içeren ağır yapı) önbelleğe almamız gerekiyordu. İlk uygulama orders.emplace(id, DeserializeOrder(buffer)) kullandı. Profil oluşturma sırasında, piyasa dalgaları sırasında, eşleşen kimlikler için hemen atılan Order nesneleri inşa etmek için CPU zamanının %15'inin boşa gittiği belirlendi.

Çözüm 1: Kontrol-edip-ekleme. Eklemeden önce if (orders.find(id) == orders.end()) kontrol etmeyi düşündük. Bu, boşa giden inşayı önledi ancak iki ağaç geçişini gerektirdi—biri bulma ve diğeri ekleme için—karşılaştırma maliyetini iki katına çıkardı ve önbellek yerelliğine zarar verdi.

Çözüm 2: Manuel düğüm çıkarımı. orders.extract(id) kullanarak manuel olarak bir std::map::node_type oluşturarak ve boşsa yeniden eklemeyi keşfettik, ancak bu, düğümü doldurmak için siparişi haritadan önce inşa etmeyi gerektirdi ve orijinal problemi yeniden ortaya çıkardı.

Çözüm 3: std::piecewise_construct. orders.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(buffer)) kullandık. Bu, serileştirmeyi, düğüm eklenmiş olduğu garanti edilene kadar erteledi. Bu tümleşik sorunu çözdü, ancak sözdizimi hacimli ve argüman ömrüyle ilgili hatalara açık hale geldi.

Seçilen yaklaşım ve sonuç: Nihayet C++17'ye geçtik ve orders.try_emplace(id, buffer) kullandık. Bu, Order'ı yalnızca başarılı ekleme ile inşa etme garantisi sağladı—daha temiz bir sözdizimi ve sarkık referansların azaltılmış riskiyle. Sistem gecikmesi, yoğun yük altında %12 azaldı.

Adayların genellikle gözden kaçırdığı şeyler

Neden std::piecewise_construct için argümanlar hazırlanırken std::make_tuple yerine std::forward_as_tuple kullanılmalıdır?

std::make_tuple, argümanlarını bozulmuş olarak demet oluşturur; değerleri demet alanına kopyalar veya taşır. Eşleşen tür kopyalanamazsa veya büyük nesneler geçiriliyorsa, make_tuple ya derlenmez ya da gereksiz kopyalama maliyetini üstlenir. std::forward_as_tuple, orijinal değer kategorisini koruyan referans demeti oluşturur (değişken veya sabit değer) ve aracısız kopyalar olmadan doğrudan nesnenin yapıcısına mükemmel iletim sağlar.

std::piecewise_construct kullanırken, std::forward_as_tuple ile sarmalanan referansların ekleme tamamlanana kadar geçerli olmasını sağlamak neden kritik öneme sahiptir?

forward_as_tuple, kendisine geçirilen geçici nesnelerin ömrünü uzatmaz; yalnızca referansları yakalar. Eğer yazarsanız map.emplace(std::piecewise_construct, std::forward_as_tuple(CreateTempKey()), std::forward_as_tuple(args...)), CreateTempKey() tarafından döndürülen geçici, tüm ifade sonunda yok edilir, emplace dahili olarak düğümü inşa etmeye çalışmadan önce. Bu, demetin bir sarkık referans tutmasına neden olur ve yapıcı anahtarı eriştiğinde tanımsız davranışa yol açar.

std::map::try_emplace ile emplace + piecewise_construct deyimi arasındaki anahtarın işlenmesi açısından fark nedir?

piecewise_construct, hem anahtarın hem de değerlerin inşasını erteleyebilse de, try_emplace açıkça anahtarı değer inşa argümanlarından ayırır. try_emplace, anahtarı referansla (veya değerle) alır ve yalnızca ekleme başarılı olursa kalan argümanları eşleşen tipin yapıcısına iletir. Bu, try_emplace'in birden fazla argümandan anahtarı yerinde inşa edemeyeceği—anahtar nesnesinin zaten var olması veya tek bir argümandan oluşturulabilir olmasını gerektirir—ancak piecewise_construct her iki bileşenin inşasını erteleyebilir. Bununla birlikte, try_emplace manuel demet yönetiminin sözdizimsel hacmini ve ömür tehlikelerini ortadan kaldırır.