1 puan yazan GN⁺ 18 시간 전 | 2 yorum | WhatsApp'ta paylaş
  • 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ü ve std::aligned_storage gibi kullanım dışı bırakma/kaldırma makaleleriyle işaretlenmiş öğeler yer alıyor; std::function da std::move_only_function, std::copyable_function, std::function_ref ile ç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ğı üreten std::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::function C++11 ile geldi; en yeni alternatif wrapper olan std::copyable_function ise 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 duruyor
  • std::function içindeki const 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_function C++23’te P0288R9, std::copyable_function C++26’da P2548R6, std::function_ref ise 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ı ve std::random_shuffle öğesini de kaldırdı
  • std::random_shuffle, std::rand ve global state’e bağımlı olduğu için std::shuffle ile 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ımlamak
  • std::aligned_storage ve std::aligned_union, C++11 ile eklendi ancak C++23’te P1413R3 ile kullanım dışı bırakıldı; typename ::type boilerplate’i, reinterpret_cast, Len == 0 için undefined behavior ve constexpr eksikliği sorun olarak gösteriliyor
  • std::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çindeki std::not_fn ile değiştirildi
  • C++11 garbage collection arayüzü olan std::declare_reachable ailesi, 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::regex C++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::async iç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 ile std::format, C++23’te P2093 ile std::print ve std::println gelmiş olsa da kullanım dışı bırakılmış değil
  • std::list, Bjarne Stroustrup’un 2012 GoingNative keynote sunumunda orta ekleme iş yüklerinde bile std::vector tarafı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üyor
  • std::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 öğe
  • std::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üyor
  • std::vector<bool>, Howard Hinnant’ın On vector<bool> yazısında öne çıkan bir analiz konusu; bit-packed depolama kendi başına yararlı olsa da std::vector specialization’ı gibi adlandırıldığı için generic kodda T = bool olduğunda yanlış davranışlara yol açan bir tuzak
  • volatile, 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ın std::unordered_map karşısında yaklaşık 3 kat performans üstünlüğü sağladığı belirtiliyor
  • Folly F14, Boost unordered_flat_map, ankerl::unordered_dense gibi alternatifler de benzer yönde ilerliyor; Rust HashMap ise hashbrown SwissTable portunu standart kütüphanenin varsayılanı olarak kullanıyor
  • std::map ve std::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; Abseil btree_map ve Rust BTreeMap ise B-tree tabanıyla aynı sorundan kaçınıyor
  • C++23, P0429R9 ile std::flat_map ve std::flat_set ekledi, ancak std::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::list bileşimini Rust’ın HashMap + BTreeMap + VecDeque bileş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::list yerine std::vector kullanmak yaklaşık 70 kat, std::unordered_map yerine flat_hash_map kullanmak 3–5 kat iyileşme sağlıyor; std::map yerine btree_map geç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 özellik
  • std::simd standarda 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=native ile derlenmiş skaler döngülerin std::simd’den daha iyi sonuç verdiği öne sürülüyor
  • std::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::regex 15 yıldır bilinen sorunlarla yaşıyor, std::deque yeniden 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 distutils modü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.toml içinden crate bazında opt-in olarak seçtiriyor; mem::uninitialized, MaybeUninit ile; std::error::Error::description, source ile; 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/ioutil paketini 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::function iç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::regex düzeltmesi, std::unordered_map öğesinin open-addressing’e geçirilmesi ve std::list, std::map, std::deque yapı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::string yerleşimi ve std::regex_traits yapı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.toml içindeki edition, Java kullanıcısı JDK sürümü, C# kullanıcısı ise net6.0 ya da net8.0 TFM seçebiliyor; ama C++’ta std:: için bir Cargo.toml yok
  • -std=c++26, hangi header’ların ve dil kurallarının kullanılacağını seçtiriyor, ancak farklı bir std::string ya da yeniden tasarlanmış bir std::unordered_map sunmuyor
  • 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

 
dieafterwork 3 시간 전

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, Drop yı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 fazla std::thread::scoped kaldırıldı. Sonrasında aynı işi sound şekilde yapan bir alternatif ortaya çıktı
    std::mem::uninitialized deprecated edildi ve artık unsound kabul ediliyor. Mevcut Range tipleri, 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 şey Display implementasyonu
    std'nin 11 yıldır kararlı olduğunu düşününce bu epey şaşırtıcı; geri kalan std hâ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üyor

    • Leakpocalypse'i bir şekilde hiç duymamıştım: faultlore (2015)
    • Iterator trait'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 problem
      Aynı şekilde f32 ve f64'ün Cmp implement etmemesi, onun yerine f32::total_cmp metoduna 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 gerekiyor
      panic 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

    • Bazı durumlarda doğru. 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 var
      Rust'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 Option benzeri API'ler “kontrol et ve hemen kullan” yaklaşımını ergonomik biçimde sunabiliyor. C++ da boş değere erişimde UB üretmeyen bir std::option sü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ı oluyor
      std::variant gibi 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üyorum
    • Bence C için de aynı şey geçerli. C'nin sorunlarının çoğu stdlib'in kötü olmasından kaynaklanıyor
      String 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
    • Şirkette, “tam olarak N baytı gösteren pointer” ya da “rastgele sayıda baytı gösteren pointer”ı ifade edebilen bir slice<T, N> implementasyonu kullanıyoruz
      head(n), tail(n), slice(start, end) ve indeks operatörü var; hepsi de sınır kontrolü yapıyor
      Bu 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
    • Basit soyutlamalarda daha az şablon kullanmaya karar verirseniz performans kaybetmiş olmaz mısınız?
  • 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::iterator kullandığınız için Slack'te azar işittiniz mi, reinterpret_cast 16 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ın

    • Bu yazı LLM ile yazılmış gibi gelmiyor
  • C++’ı 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 ranges kü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ığı olan Iterator metotlarının sayısı 76, bir kez cargo add yapınca itertools trait’iyle buna 130 tane daha ekleniyor.
    Gerçekten özlediğim bir diğer şey de pattern matching. std::variant gibi 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.

    • C++’ın sorunlarından biri, hangi özelliğin dil özelliği, hangisinin standart kütüphane özelliği olması gerektiğine dair resmî ve belgelenmiş ölçütlerin olmaması.
      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::string bir 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::embed bunun 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.

    • C++ ve Haskell’in ikisi de komite tarafından tasarlandı, ama iki dil neredeyse birbirinin tam zıttı. “$X komite tarafından tasarlandı” ifadesinin $X hakkında tek başına bir şey ima ettiğini düşünmeye başladığım her seferinde bunu hatırlıyorum.
  • 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

    • Bu tamamen doğru değil. C++, standart kütüphaneyi dilden bağımsız olarak sürümlemiyor sadece
      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 pImpl desenini 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” der
      Standardı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-exceptions ya 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-exception ile derlerseniz doğrudan abort ç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_back programı crash ettirebilir
      Makaledeki “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
    • Burada go vet de değerli. Çünkü API iyileştirmeleri için otomatik yükseltme sunuyor
  • Geç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/ioutil de 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ğil

  • C++ 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_list gibi bazı şeylerin sözdizimine gömülü olduğunu biliyorum ama onun dışındakilerin hepsi değiştirilebilir