C++ standart kütüphanesi 15 yıldır kendi adımlarını geri alıyor ve kanıtlar açıkta
(hftuniversity.com)- C++ standart kütüphanesi, C++11’den bu yana hatalı tasarımları resmen kullanımdan kaldırma ya da yeni alternatiflerinin yanında olduğu gibi bırakma döngüsünü tekrarladı; bu da geliştiricilerin “kullanılmaması gereken katmanların” hangi döneme ait olduğunu bilmesini gerektiren bir yapı oluşturuyor
- Resmî geri çekilme katmanında
std::auto_ptr, dinamik exception specification’lar, C++11 garbage collection arayüzü vestd::aligned_storagegibi kullanım dışı bırakma/kaldırma makaleleriyle işaretlenmiş öğeler yer alıyor;std::functiondastd::move_only_function,std::copyable_function,std::function_refile çevrelenmiş 15 yıllık bir ikame akışının içinde bulunuyor - Gayriresmî kaçınma katmanında yavaş
std::regex, destructor içinde bekleyerek deadlock tuzağı üretenstd::async,<iostream>,std::list,std::deque,std::vector<bool>gibi standartta kalmasına rağmen üretim kodunda etrafından dolaşılan özellikler bulunuyor - Varsayılan container sorunu özellikle
std::unordered_map,std::map,std::listüzerinde belirginleşiyor; aynı iş yükü benchmark’ında C++ saf uygulamasının P99 değeri 302,653 cycles iken Rust saf uygulaması 5,177 cycles ile 58 kat fark gösteriyor - ABI kararlılığı tercihi, diğer dillerin silme, edition ya da büyük sürüm geçişleriyle hataları azaltmasına karşılık C++’ın hatalı varsayılanlarını
std::içinde fiilen kalıcı olarak korumasındaki temel farkı oluşturuyor
Başlangıç noktası: std::function için “legacy” hükmü
- Sandor Dargo’nun hızlı referans tablosu
std::copyable_function,std::functionöğesini “Legacy. Avoid in new code.” olarak sınıflandırıyor std::functionC++11 ile geldi; en yeni alternatif wrapper olanstd::copyable_functionise C++26 ile geliyor ve yeni özelliğin öneri noktası “kopyalanabilir callable nesne gerektiğinde bunu kullanın” demekten çok “eskisini kullanmayın” noktasına yakın duruyorstd::functioniçindekiconst operator(), non-const callable nesneleri çağırabilen bir const-correctness kusuruna sahip ve ABI bozulmadan düzeltilemez durumda- Bu kusura yanıt olarak
std::move_only_functionC++23’te P0288R9,std::copyable_functionC++26’da P2548R6,std::function_refise C++26’da P0792R14 akışında yer alıyor
Resmen geri alınan standart özellikler
std::auto_ptr, copy-move semantiği generic kodu ve standart container’ları bozduğu için C++11’de kullanım dışı bırakıldı, C++17’de N4190 ile kaldırıldı; aynı makale C++98<functional>adapter’larını vestd::random_shuffleöğesini de kaldırdıstd::random_shuffle,std::randve global state’e bağımlı olduğu içinstd::shuffleile değiştirildi- Dinamik exception specification
throw(X, Y), C++11’de kullanım dışı bırakıldı, C++17’de P0003R5 ile kaldırıldı;throw()takma adı ise C++20’de P1152 ile kaldırıldı std::iterator, C++17’de P0174R2 ile kullanım dışı bırakıldı ve C++26’dan kaldırılması P3365R1 ile ilerletiliyor; alternatif yöntem beş typedef’i doğrudan tanımlamakstd::aligned_storagevestd::aligned_union, C++11 ile eklendi ancak C++23’te P1413R3 ile kullanım dışı bırakıldı;typename ::typeboilerplate’i,reinterpret_cast,Len == 0için undefined behavior ve constexpr eksikliği sorun olarak gösteriliyorstd::not1,std::not2,unary_negate,binary_negate, C++17’de kullanım dışı bırakıldı, C++20’de kaldırıldı ve P0005 içindekistd::not_fnile değiştirildi- C++11 garbage collection arayüzü olan
std::declare_reachableailesi, büyük implementasyonların gerçek bir garbage collector sunmaması nedeniyle C++23’te P2186R2 ile kaldırıldı - Concepts TS, Modules TS, Coroutines TS, Reflection TS, Executors TS ve Networking TS de birleştirilmeden önce yeniden tasarım, değiştirme ya da gecikme süreçlerinden geçti; Reflection P2996, Executors ise P2300 sender/receiver akışına dönüştü
Standartta kalıp sahada kaçınılan özellikler
std::regexC++11 ile geldi, ancak P1844R1 komite kayıtlarına “eldeki diğer çözümlere göre performansı çok kötü” notunu düşüyor; alternatif akış CTRE ve P1433R0, standart dışında ise Boost.Regex, RE2 ve PCRE2 tarafıstd::asynciçinde dönen future nesnesinin destructor’ı, asenkron iş tamamlanana kadar bloklanıyor; N3679 bunun yarattığı deadlock tuzağını belgeliyor<iostream>yavaş, locale’e bağlı, formatting sırasında thread-safe değil ve hata mesajları kötü şöhrete sahip; buna rağmen C++20’de P0645 ilestd::format, C++23’te P2093 ilestd::printvestd::printlngelmiş olsa da kullanım dışı bırakılmış değilstd::list, Bjarne Stroustrup’un 2012 GoingNative keynote sunumunda orta ekleme iş yüklerinde bilestd::vectortarafından geçildiği gösterilen öğelerden biri; devam yazısı Are lists evil? da cevabın “evet”e yakın olduğunu söylüyorstd::deque, Microsoft STL’nin açık issue’su microsoft/STL#147 içinde standardın zorladığı blok boyutunun fazla küçük olduğu ve bir sonraki ABI break sırasında büyük bir performans revizyonu gerektiği kaydedilen bir öğestd::valarray, 1998’de sayısal container olarak eklendi ama expression template optimizasyonları gerçekleşmedi; cppreference’a göre implementasyonların çoğu genel amaçlı container’ların ötesinde özel bir kod taşımıyor gibi görünüyorstd::vector<bool>, Howard Hinnant’ın Onvector<bool>yazısında öne çıkan bir analiz konusu; bit-packed depolama kendi başına yararlı olsa dastd::vectorspecialization’ı gibi adlandırıldığı için generic koddaT = boololduğunda yanlış davranışlara yol açan bir tuzakvolatile, C++20’de P1152R4 ile bileşik işlemlerde ve parametre/dönüş konumlarında kullanım dışı bırakıldı, sonra C++23’te P2327R1 ile kısmen geri alındı; C++26’da P2866R0 ile ek geri alma bekleniyor
ABI yüzünden düzeltilemeyen varsayılan container’lar
std::unordered_map, C++11 spesifikasyonundaki bucket ve iterator kararlılığı nedeniyle open addressing’i fiilen yasaklıyor; Google SwissTable yapısınınstd::unordered_mapkarşısında yaklaşık 3 kat performans üstünlüğü sağladığı belirtiliyor- Folly F14, Boost
unordered_flat_map,ankerl::unordered_densegibi alternatifler de benzer yönde ilerliyor; RustHashMapise hashbrown SwissTable portunu standart kütüphanenin varsayılanı olarak kullanıyor std::mapvestd::set, node tabanlı red-black tree olduğu için her node için heap allocation gerektiriyor ve her dolaşımda pointer takibi yapıyor; Abseilbtree_mapve RustBTreeMapise B-tree tabanıyla aynı sorundan kaçınıyor- C++23, P0429R9 ile
std::flat_mapvestd::flat_setekledi, ancakstd::unordered_map,std::map,std::listöğelerinin temel varsayılan tasarımını değiştiremedi - multi-symbol order book benchmark, aynı iş yükü, aynı seed ve aynı izole çekirdekte C++’ın
std::unordered_map+std::map+std::listbileşimini Rust’ınHashMap+BTreeMap+VecDequebileşimiyle karşılaştırıyor
| Uygulama | P99 cycles |
|---|---|
C++ saf (unordered_map + map + list) |
302,653 |
C++ adım 1 (flat_hash_map + map + deque) |
9,951 |
C++ adım 2 (flat_hash_map + btree_map + deque) |
9,114 |
C++ adım 3 (flat_hash_map + btree_map + vector) |
4,268 |
Rust saf (HashMap + BTreeMap + VecDeque) |
5,177 |
- Yalnızca
std::listyerinestd::vectorkullanmak yaklaşık 70 kat,std::unordered_mapyerineflat_hash_mapkullanmak 3–5 kat iyileşme sağlıyor;std::mapyerinebtree_mapgeçişi ise 1.09 kat ve gürültü aralığında kalan bir etki gösteriyor - Karşılaştırmanın odağı, Rust dilinin kendi başına C++’tan 58 kat hızlı olduğu değil; Rust standart kütüphanesinin doğru varsayılanları seçmiş olması, C++ standart kütüphanesinin ise ABI nedeniyle bu üç varsayılanı düzeltememesi
Vasa problemi ve özellik birikimi
- Bjarne Stroustrup’un 2018 tarihli WG21 belgesi P0977R0 “Remember the Vasa!”, 1628’de batan İsveç savaş gemisi Vasa’yı benzetme olarak kullanıyor ve komitede “yaklaşık 150 aşçı” bulunduğunu, tek tek özelliklerin tüm sistem üzerindeki etkisinin yeterince ele alınmadığını söylüyor
std::simd, std::simd Is a Solution to the Wrong Problem yazısında aynı örüntünün öne çıkan bir örneği olarak ele alınıyor; Matthias Kretz’in Vc kütüphanesinden başlayıp P0214, Parallelism TS 2 ve P1928 üzerinden C++26’ya taşıdığı bir özellikstd::simdstandarda girerken standart dışında Google Highway, ISPC, EVE, xsimd ve SIMDe zaten vardı; ayrıca GCC ve Clang auto-vectorizer’ları da gelişmişti ve-O3 -march=nativeile derlenmiş skaler döngülerinstd::simd’den daha iyi sonuç verdiği öne sürülüyorstd::simd, eşdeğer skaler koda göre derlemeyi 10 kat yavaşlatıyor, yerini alması beklenen auto-vectorizer’dan daha yavaş kalıyor ve ARM SVE’nin scalable-width vector’larını ve runtime dispatch’i ifade edemiyor- libstdc++, libc++ ve MSVC STL adlı üç implementasyonun her biri tek haneli büyüklükte mühendis ekiplerince sürdürülüyor; her yeni standart özellik test matrisi, uygunluk hataları, özellikler arası etkileşimler ve bir sonraki bakımcının devralacağı bug tracker kalemlerini artırıyor
std::regex15 yıldır bilinen sorunlarla yaşıyor,std::dequeyeniden tasarım gerektiren bir issue taşıyor ve C++20 modules, standardizasyondan 6 yıl sonra bile üç implementasyonun tamamında temiz biçimde çalışan bir durumda değil diye tarif ediliyor- Modern C++ standardının gerçek kullanım bilgisi; hangi dönemde hangi hatalı katmanın bulunduğu, hangi third-party kaçış yollarının kullanıldığı, üç standart kütüphane implementasyonu arasındaki farklar ve teoriyle pratiğin ayrımı gibi bilgileri öğrenmiş az sayıdaki tam zamanlı uzmanın elinde yoğunlaşıyor
Diğer dillerden farkı: hata yapmak değil, koruma oranı
- Python, PEP 594 ile 20’den fazla standart kütüphane modülünü kaldırdı; PEP 632 ile
distutilsmodülünü Python 3.12’den çıkardı ve PEP 387 tehlikeli ya da bozuk özellikler için kullanım dışı bırakma döngüsünü kısaltma yetkisini tanımlıyor - Java, Applet API’yi Java 9’da kullanım dışı bıraktı, Java 17’de kaldırılmak üzere işaretledi ve JEP 504 ile gerçek kaldırmaya uzanan 8 yıllık bir yol izledi; Nashorn da JEP 372 ile Java 15’te kaldırıldı
- Java SecurityManager, JEP 411 ile kaldırılmak üzere kullanım dışı bırakıldı ve JEP 486 ile kalıcı olarak devre dışı bırakıldı; JEP 398 ise Applet API’nin kaldırılma yolunu ele alıyor
- Rust, 2015, 2018, 2021 ve 2024 edition’larını
Cargo.tomliçinden crate bazında opt-in olarak seçtiriyor;mem::uninitialized,MaybeUninitile;std::error::Error::description,sourceile;try!makrosu ise?operatörü ile değiştirildi - C#, .NET Framework’ten .NET Core’a geçerken BinaryFormatter, AppDomains, Remoting, Code Access Security, WCF server ve WebForms gibi bileşenleri geride bırakan büyük sürüm geçişlerini göze aldı
- JavaScript, web uyumluluğu kısıtları nedeniyle neredeyse hiçbir şeyi kaldırmıyor; yine de cancelable promises Stage 1’de geri çekildi, SIMD.js WebAssembly SIMD lehine bırakıldı ve Go, Go 1 uyumluluk sözü nedeniyle
io/ioutilpaketini yalnızca kullanım dışı bırakılmış durumda tutmayı seçti - C++’ın farkı, hata yapmış olması değil;
std::regex,std::unordered_map,std::vector<bool>,std::valarray,std::functioniçindeki const-correctness kusuru gibi öğeleri neredeyse hiç kaldıramamasındaki yüksek koruma oranı
ABI kararlılığının yarattığı kalıcı koruma
- P1863R1 “ABI - Now or Never”, C++23’te ABI break göze alınıp alınmayacağını ya da kalıcı ABI kararlılığının mı seçileceğini soran bir akıştı; komite fiilen kalıcı ABI kararlılığını seçti
- Bu tercih yüzünden
std::regexdüzeltmesi,std::unordered_mapöğesinin open-addressing’e geçirilmesi vestd::list,std::map,std::dequeyapılarının değiştirilmesi zorlaştı - C++ standart kütüphanesi ABI’si dinamik linker tarafından zorlanıyor; bir libstdc++ sürümüyle derlenmiş nesnenin başka bir sürümün nesnesiyle bağlanabilmesi gerektiği için
std::stringyerleşimi vestd::regex_traitsyapısı gibi ayrıntılar dağıtılan binary’lere kazınmış durumda - Bu kısıt, libstdc++ ABI policy ve Itanium C++ ABI gibi belgelerde somutlaşıyor
- Python kullanıcısı
python==3.12, Rust kullanıcısıCargo.tomliçindeki edition, Java kullanıcısı JDK sürümü, C# kullanıcısı isenet6.0ya danet8.0TFM seçebiliyor; ama C++’tastd::için birCargo.tomlyok -std=c++26, hangi header’ların ve dil kurallarının kullanılacağını seçtiriyor, ancak farklı birstd::stringya da yeniden tasarlanmış birstd::unordered_mapsunmuyor- Bu yüzden 2026’da üretime çıkan C++ standart kütüphanesi, 1998’den bu yana komitenin kabul ettiği hatalı varsayılanları tasarımı ve zorlayıcı yapısı gereği taşımayı sürdürüyor
- Birinci sınıf trading firm’leri, arama motorları ve tarayıcıların modern C++ kod tabanları; Boost, Abseil, Folly, EASTL, Chromium
//base, elde yazılmış container’lar, custom allocator’lar, CTRE, Outcome ve coroutine kütüphaneleri gibi standart dışı kütüphanelere büyük ölçüde dayanıyor
2 yorum
Asıl metin epey dolu; ama sonuna kadar okuyunca Rust’a bağlılık hissi biraz güçlü geliyor.
Yine de bilmediğim birçok şeyi öğrenmiş oldum. Güzel yazı için teşekkürler.
Lobste.rs görüşleri
Rust ekosisteminde de benzer bir churn olup olmadığını düşününce, büyük olanların yalnızca birkaç tane olduğu görülüyor
Leakpocalypse sırasında,
Dropyıkıcılarına güvenlik değişmezlerini korumak için her zaman çalışacaklarmış gibi güvenilemeyeceği sonucuna varıldı; fiili API değişikliği ise neredeyse yoktu, en fazlastd::thread::scopedkaldırıldı. Sonrasında aynı işi sound şekilde yapan bir alternatif ortaya çıktıstd::mem::uninitializeddeprecated edildi ve artık unsound kabul ediliyor. MevcutRangetipleri, nispeten küçük API sorunlarını düzeltmek için neredeyse aynı olan yeni tiplerle yavaş yavaş değiştirilecek.std::error::Error::description, çoğu hata tipinin bir string saklamak istememesi nedeniyle deprecated edildi ve doğrudan yerine kullanılabilecek şeyDisplayimplementasyonustd'nin 11 yıldır kararlı olduğunu düşününce bu epey şaşırtıcı; geri kalanstdhâlâ var ve çalışıyor, %98'i de hâlâ idiomatik Rust kabul ediliyor. Buna karşılık C++ standart kütüphanesi, özellik ekleme konusunda fazlasıyla tetiği hafif, ama hiçbir koşulda deprecated etme konusunda şaşırtıcı derecede muhafazakâr olan tehlikeli bir konumda görünüyorIteratortrait'inin kendi içeriğini ödünç alması sorunu da aklıma geliyor. Rust tartışmalarında sürekli “bunu neden kullanamıyoruz da workaround gerekiyor” diye ortaya çıkan kronik bir problemAynı şekilde
f32vef64'ünCmpimplement etmemesi, onun yerinef32::total_cmpmetoduna sahip olması da yeni mühendislerin sık sık takıldığı can sıkıcı bir nokta; bu yüzden iç çekerek arka planı açıklamak gerekiyorpanic biçimlendirme düzeneği de pek iyi değil; varsayılan panic handler'ın biçimlendirme kullanması ve bunun kapatılmasının zor olması nedeniyle çalıştırılabilir dosya boyutunu epey büyüttüğüne dair çok sayıda blog yazısı var
Kişisel olarak standart kütüphanenin eski tasarımının C++'ın popülerliğini ve kullanılabilirliğini ciddi biçimde düşürdüğünü düşünüyorum
Dilin kendisine yüklenen pek çok sorun aslında standart kütüphaneye yöneltilmeli
Örneğin “C++ derlemesi yavaştır” sözü doğru değil. C++ özelliklerini kullanmak özünde yavaş değil; büyük miktarda header şişkinliği ve bağımlılık ile, basit soyutlamalar için bile şablonları aşırı kullanan standart kütüphane yavaşlığa neden oluyor
“C++ güvenli değil” sözü de kısmen doğru, ama standart kütüphane tasarımı bunu daha da kötüleştiriyor. Rust API tasarımında kullanılan daha güvenli kalıpların yeni bir standart kütüphaneye uygulanamaması için bir neden yok. Elbette C++'ın büyük avantajlarından biri geriye dönük uyumluluk, dolayısıyla bu çok karmaşık bir mesele
vec[idx]'i sınır dışı erişim/tanımsız davranış yerine istisna fırlatacak ya da abort edecek şekilde yapmak mümkün. Ama dil farkları nedeniyle C++'ta güvenli API'ler yapmak çok daha zor olan pek çok durum da varRust'ta varsayılan olarak yıkıcı taşıma var, C++'ta ise yok. Bu yüzden akıllı işaretçi API'leri ya unsafe olmak ya da en azından şaşırtıcı ve crash-y olmak zorunda kalıyor. Örneğin moved-from bir akıllı işaretçiye erişildiğinde programın abort etmesi gibi
Rust'ta yaşam süresi açıklamaları var, C++'ta yok. Bu yüzden Rust, iterator API tasarımında iterator invalidation gibi şeyleri engelleyebilirken C++'ta bu fiilen çok zor. Rust'ta pattern matching olduğu için
Optionbenzeri API'ler “kontrol et ve hemen kullan” yaklaşımını ergonomik biçimde sunabiliyor. C++ da boş değere erişimde UB üretmeyen birstd::optionsürümü sunabilir, ama bu hem bugünkü C++'tan hem de Rust'tan çok daha kullanışsız olurdu. Rust'ın?operatörü de burada büyük yardımcı oluyorstd::variantgibi overload set'lerle pattern matching'e benzer bir şeyin C++'a eklenebildiğini biliyorum, ama bunun çok daha zor kullanıldığını ve hata yapmaya daha açık olduğunu düşünüyorumString ve dizi kütüphanesi, birkaç jenerik konteyner ve allocator için yerel destek sunan modern bir kütüphane bile C'yi çok daha ergonomik ve kullanımı kolay hâle getirebilir. Elbette dilin bazı kusurları yalnızca kütüphaneyi değiştirerek ortadan kalkmaz, ama yine de oldukça ileri gidilebilir
Modern C kod tabanlarına bakınca allocator, string, vector, hash table ve dosya sistemi işlemleri için özel kütüphanelerin yaygın biçimde kullanıldığını görüyorsunuz; C ya da manuel kaynak yönetimi deneyiminiz varsa bu çizgiyi takip etmek de zor değil
slice<T, N>implementasyonu kullanıyoruzhead(n),tail(n),slice(start, end)ve indeks operatörü var; hepsi de sınır kontrolü yapıyorBu tür soyutlamalarla çalışmak gerçekten keyifli, ama modern ve bir ölçüde güvenli bir dil elde etmek için pratikte Rust ve Zig standart kütüphanelerini C++'a port etmek gerekiyor. Yine de sonuçta harcanan emeğe değiyor
Böyle bir yazı yazılacaksa keşke doğrudan kendiniz yazsaydınız. Listeyi belki gerçekten siz hazırladınız, ama onu bir LLM'e verip çıkan sonucu insanların okuyacağı bir web sayfasına koymak çok kaba. “Çalışan mühendisler”in “ilk günden” itibaren “özellik X”ten kaçınmaları gerektiğini öğrendiği cümlesini bir kez daha görürsem çıldıracağım gibi hissediyorum
Utanç verici olan, burada söylenecek gerçekten çok şey varken fiilen hiçbir şey söylenmemesi. Bu yazıyı üretmenizin bir nedeni olmalı; o nedeni söylemenizi isterdim. C++'ın bir yönü sizi kızdırmış olmalı ve bir özellik kafanızı karıştırmış olmalı. Bu özelliklerin kötü olmasının sebebi yalnızca nesnel tasarım başarısızlıkları değil, bizi nasıl etkiledikleridir
std::iteratorkullandığınız için Slack'te azar işittiniz mi,reinterpret_cast16 harf olduğu için satır biçimini biraz bozacak diye cast kullanmamayı seçtiniz mi; bunlar Lobsters'ta yer alsaydı çok daha iyi olurdu. Böyle hikâyeler yoksa zorla uydurmayın; bir GPU'ya matris çarpımı yaptırıp aynı cümleyi 10 kez yazdırmayın. Yorum yapılacak kısımları sadece açıklama ekleyin, geri kalanını da tablo ve bullet point olarak yazınC++’ı 20 yıldır kullanıyorum ve hâlâ kullanıyorum, ama bu yazıya büyük ölçüde katılıyorum. Bugünlerde Rust kullanırken gerçekten hoşuma giden şey, bellek güvenliğinden çok harika standart kütüphane ve paket ekosistemi.
Bunun tipik bir örneği
rangeskütüphanesi. Standartlaşmasının üzerinden 6 yıl geçmiş olmasına rağmen büyük standart kütüphaneler hâlâ bunu tam olarak implemente edebilmiş değil; implemente edildiğinde bile sadece birkaç combinator var. Rust’taki karşılığı olanIteratormetotlarının sayısı 76, bir kezcargo addyapıncaitertoolstrait’iyle buna 130 tane daha ekleniyor.Gerçekten özlediğim bir diğer şey de pattern matching.
std::variantgibi union tiplerini ergonomik hâle getirebiliyor. Teklif hâlâ tartışılıyor ama C++26’ya da henüz girmedi; bu da üzücü. Buna karşılık contracts ve executors geliyor, ama dürüst olmak gerekirse çevremde bunları isteyen kimse görmedim.Genel olarak benim baktığım ölçüt şu. Bir özellik arzu edilen kullanım senaryolarını destekliyorsa ve standart kütüphane olarak ifade edilemiyorsa dile girmeli. Mümkünse istenen özelliği, başka amaçlar için de kullanılabilecek en küçük bağımsız parçalara ayırmak gerekir.
Neredeyse tüm codebase’lerde kullanılan özellikler standart kütüphaneye girmeli. Bir tip, kütüphaneler arası arayüz olarak sık kullanılıyorsa standart kütüphanede yer almalı. Her kütüphanenin kendi tuple tipini ya da string’ini tanımlamasını istemeyiz. C++ birincisinde C++11’e kadar fiilen böyleydi; ikincisinde ise
std::stringbir disaster olduğu için hâlâ böyle. Bu, arayüz tipleri için de geçerlidir ve C++ bugünlerde bunu çoğunlukla concepts ile ele alıyor.Geri kalanlar yeniden kullanılabilir modüler kütüphanelerde olmalı. Rust, istikrarlı ve blessed dış kütüphaneler kümesi oluşturma konusunda oldukça iyi olduğu için, “Rust ile yazılan her oyunun bu veri yapısına ihtiyacı var, o hâlde bunu standart kütüphaneye koyalım” baskısı çok daha zayıf. Oyun geliştirenler ihtiyaç duydukları crate’i alıp kullanabiliyor. C++, “çok sayıda ama çoğunluk olmayan insanın sahip olduğu problemlere önerebileceğimiz iyi paketler” fikrini hiçbir zaman gerçekten benimsemedi.
Endişe verici olan şey, şu anda eklenmekte olanlar arasından hangilerinin sonunda geri alınacağı. Contracts daha yeni C++26’ya girdi ama şimdiden ciddi tasarım kusurlarına işaret ediliyor.
Genel olarak “komite tasarımı”nı kötülemek istemem. Bu tür yapıların önemli bir amacı yerine getirdiğini ve kendine özgü güçlü yanları olduğunu düşünüyorum. Ama bu güçlü yanları, tamamen yeni özellikleri sıfırdan tasarlamakta değil.
WG21 ve WG14’ün gerçekten parladığı nokta, tasarım alanı belli ölçüde keşfedilmiş ve mümkünse birden fazla mevcut implementasyonu olan özellikleri alıp, kullanıcıların ve implementasyon yapanların çoğunun kabul edebileceği standart özelliklere dönüştürmek.
std::embedbunun bir örneği.Buna karşılık, yazıda geçen GC uzantıları,
std::memory_order_consume, C++20 modules gibi şeyler, biri bunları gerçekten düzgün biçimde implemente etmeden önce standartlaştırıldığında işler ciddi biçimde kötüye gitme eğiliminde oluyor.Bir süre önce C++’ın standart kütüphaneyi sürümlemediğini fark ettiğimde epey sarsılmıştım. Bu yazının tam da bu noktaya parmak basacağını bilmiyordum
Go’nun da forward compatibility konusunda benzer şekilde muhafazakâr olduğunun belirtilmesi ilginçti. Ama Go, eklenen özelliklerde de benzer biçimde muhafazakâr olduğu için C++’ın sorunlarının çoğundan kaçınmış gibi görünüyor. Kararlı bir ABI’sinin olmaması da muhtemelen yardımcı olmuştur
Bildiğim popüler kütüphaneler arasında açıkça C++ ABI’si sunan tek şey libcamera; bu da epey can sıkıcı. Benim deneyimimde C++ kütüphaneleri de genelde sembollerini C ABI olarak dışa aktarır ve bu da diğer dillerle birlikte çalışmayı kolaylaştırır. Belki de gelişmeleri kaçırmış olabilirim
Bir de Clang ile MSVC arasında ABI uyumluluğunda bazı tuhaflıklar yok mu? Conan’ın derleyici karıştırmayı açıkça caydırdığını ya da yasakladığını hatırlıyorum; bu yüzden C++ komitesinin neden ABI kararlılığını korumaya bu kadar uğraştığını merak ediyorum
Burada birbirine çok yakın iki şey var: standart kütüphane spesifikasyonu ve implementasyonu. Spesifikasyon, tam bir dil+kütüphane birleşimi içindir; implementasyon ise genelde en az bir veya daha fazla spesifikasyon sürümünü desteklemeye çalışır
C++ arayüzü sunan çok sayıda kütüphane var; Qt gibi çok büyük olanlar da buna dahil
Sorun, C++ soyut makinesinin bağlama sürecini tanımlamaması. Bu yüzden dinamik kütüphanelerin nasıl çalıştığını tanımlayamıyor. UNIX sistemlerinde C++ dinamik bağlama C modelini izler. Dinamik bağlama varmış gibi yapıp işi loader sorunlarına bırakır. Bu da copy relocation gibi korkunç şeylere yol açar. Windows, paylaşımlı kütüphanenin ne olduğu konusunda çok daha ilkesel bir kavrama sahip, ama bu yüzden UNIX C++ kütüphanelerindeki bazı idiom’lar Windows’ta çalışamaz
Paylaşımlı kütüphaneler, C++ template’leri gibi özelliklerde büyük sorun çıkarır. Kullanıcı tipleriyle template instantiate edebilmek için tüm tanımın header içinde olması gerekir, çünkü derleyici compilation-unit sınırlarının ötesini göremez. Paylaşımlı kütüphanelerde aynı kod birden fazla yerde instantiate edilir. Program ve kütüphane aynı parametrelerle aynı template’i instantiate ederse, ikisinin de birer kopyası olur; sonra linker ve loader’ın nihai yüklenen programda bunlardan yalnızca birinin kullanılmasını sağlaması gerekir
Swift ile karşılaştırınca, Swift açıkça “paylaşımlı kütüphaneler vardır ve bunları ifade eden dil düzeyinde yapılar sunar” der. Paylaşımlı kütüphane sınırları üzerinden generic’leri dışa açmak istiyorsanız bu mümkündür, ama tüm harici çağıranlar için dinamik dispatch sürümüne indirilir. Bunu C++’ta da elle yapmak mümkündür. Type erasure wrapper kullanan genel bir template sürümü oluşturur ve farklı somut instantiation’ları açıkça yazarsınız. Ama bu zor ve manueldir. Swift’te ise sadece “paylaşımlı kütüphane sınırında işler böyle yürür” denir
Type hiding de aynı şekilde. C++, kütüphane sınırının ötesine davranışı açıp implementasyonu gizleyen bir public interface oluşturmak için
pImpldesenini kullanır. Swift ise kütüphane sınırlarının nerede olduğunu bilen bir soyut makineye sahiptir ve “ABI-stable olarak açıkça belirtilmemiş bir tipin boyutu, paylaşımlı kütüphane sınırının ötesinde derleme zamanında sabit değildir” derStandardın gerçekliği inkâr etmesinin başka bir biçimi de bu. Üzerinde çalıştığım neredeyse her non-trivial C++ kod tabanı
-fno-rtti -fno-exceptionsya da CL.EXE’deki karşılığıyla derlenmişti. Standart bunun bir olasılık olduğunu kabul etmiyor. Standart kütüphanedeki işlevlerin çoğu hata bildiriminde hâlâ exception beklediğinden,-fno-exceptionile derlerseniz doğrudanabortçağırıyor. Bu yüzden dinamik bellek tahsisi yapan standart kütüphane bileşenleri embedded ortamda kullanılamaz hale geliyor.std::vector<T>::push_backprogramı crash ettirebilirMakaledeki “komite kötü özellikleri kaldıramamakla kalmıyor, uygulamadaki mühendislerin istemediği yeni özellikleri de eklemeyi sürdürüyor” kısmı, contracts’ın ortaya çıkış biçimiyle %100 aynı. Verus, C++ benzeri ortamları hedefleyen bir dilde iyi bir contracts sisteminin neleri mümkün kılabildiğini gösteriyor. P2900 contracts, birbiriyle çelişen gereksinimlerin bir bileşimi; bu yüzden contracts’ın uygun olabileceği her sorunu daha kötü hale getiriyor
“C++ mühendisi”nin “programlama yapabilen mühendis”ten çok daha yüksek ücret aldığı sonucu ise bence doğru değil. Gerçekte kimse C++ standardını olduğu gibi kullanarak kod yazmıyor; herkes kendi sevdiği şirket içi subset-of-a-superset’e göre yazıyor
go vetde değerli. Çünkü API iyileştirmeleri için otomatik yükseltme sunuyorGeçen yıldan beri C++’ı neredeyse tamamen bıraktım; önce Kotlin’e, sonra Swift’e geçtim. İşte hâlâ C++ bakımı yapmam gerekiyor ama yeni yazdığımız kodlar çok daha temiz, özlü ve güvenli. Kod boyutu ve belki performans tarafında tradeoff var, ama buna değiyor
Go’nun for loop semantiğinin geriye dönük uyumluluğu bozacak şekilde değiştiğini hatırladığım için şu cümlenin yanlış olduğunu düşünmüştüm: https://go.dev/blog/loopvar-preview
Ama sonra gördüm ki Go burada da Rust editions’a benzer bir yaklaşım kullanıyor. Semantiğin değişmesi için Go sürümünü 1.22 veya üstü olarak belirtmeniz gerekiyor. Muhtemelen
io/ioutilde bu şekilde kaldırılabilirdi, ama görünüşe göre edition sınırlarını aşacak kadar kod kırmaya değecek bir şey değilC++ bu kötü fikirleri gerçekten denemiş ve bunların kötü fikir olduğunu kanıtlamış olmasaydı, Rust bugün bildiğimiz haliyle var olmayabilirdi. Büyük bir teşekkür!
C++ için Rust tarzı standart kütüphane alternatifi ile ilgileniyorum. Bunu hedefleyen rpp’yi biliyorum: https://github.com/TheNumbat/rpp
Başka seçenekler var mı? EASTL gibi C++ stdlib’in farklı implementasyonlarını değil, Rust’a daha yakın duran kütüphaneleri kastediyorum.
std::initializer_listgibi bazı şeylerin sözdizimine gömülü olduğunu biliyorum ama onun dışındakilerin hepsi değiştirilebilir