C++20'den önce, constexpr belirteci sanal fonksiyon çağrılarını kesin olarak yasaklı hale getirdi çünkü sabit değerlendirme, çalışma zamanı dolaylılığını önlemek için derleme zamanında tam tür bilgisi gerektiriyordu. C++20 standardı, sabit değerlendirme sırasında dinamik türlerin izlenmesini sağlayarak, derleme zamanı yorumlayıcısı içinde simüle edilmiş vtable aramaları ile sanal dağıtımın yapılmasına olanak tanıyarak bu kısıtlamaları temelden gevşetti. Ancak, standart, temel sınıf işaretçileri üzerinde delete ifadelerinin sabit ifadelerde geçersiz olduğu gerçeğiyle birlikte constexpr polimorfik silme işlemi için sıkı bir yasak korumaya devam ediyor çünkü temel ::operator delete uygulanması constexpr yetenekli değildir ve çalışma zamanı bellek ayırıcı ile etkileşime girer, bu da geçiş sırasında kararlı depolama bellek boşaltımını imkansız hale getirir.
Çözüm, constexpr sanal fonksiyonların, sabit bağlamlarda polimorfik algoritmaları etkinleştirmenin yanı sıra, temel sınıf işaretçilerine üzerinde açık delete ifadelerinin hâlâ sabit ifadelere geçersiz olduğunu anlamaktan geçer. Bu ayrım, geliştiricilerin metaprogramlama ve statik yapılandırmalar için kalıtım hiyerarşilerini kullanmalarına olanak tanırken, kaynak yönetiminin hâlâ çalışma zamanında veya otomatik depolama süresiyle gerçekleştirilmesi gerektiğini kabul etmelerini sağlıyor. Sonuç olarak, constexpr sanal yıkıcılar otomatik nesnelerin temizliği için kabul edilir, ancak dinamik tahsis desenleri constexpr değerlendirme yolunda delete çağrısında bulunmayan std::unique_ptr veya benzeri sarıcılara ihtiyaç duyar.
struct Base { virtual constexpr int compute() const { return 1; } virtual constexpr ~Base() = default; }; struct Derived : Base { constexpr int compute() const override { return 42; } }; constexpr int test() { Derived d; Base* ptr = &d; return ptr->compute(); // Geçerli C++20: 42 döner } // Geçersiz: delete ptr; constexpr bağlamında derlenmeyecek static_assert(test() == 42);
Bir finansal ticaret firması, donanım hızlandırıcılarına doğrudan gömüldüğü risk matrislerini iliştirmek için karmaşık türev fiyatlandırma modellerini derleme zamanında hesaplaması gerekti. Mevcut C++17 kod tabanı, sanal price() yöntemleri ile polimorfik bir Instrument hiyerarşisi kullanıyordu ancak geliştiriciler, sanal fonksiyonların constexpr değerlendirmelerinden yasaklı olması nedeniyle bu temiz tasarımdan vazgeçmeye zorlandılar. Bu mimari kısıtlama, ekibin bakımı kolay nesne yönelimli kod ile statik başlatmanın performans avantajları arasında seçim yapma zorunluluğunu doğurdu.
İlk yaklaşım, sanal fonksiyonları statik yönlendirmelerle değiştiren şablon tabanlı statik polimorfizm kullanmaktı. Bu çözüm, sıfır çalışma zamanı maliyeti ve tam C++17 uyumluluğu sunmasına rağmen, alan modelini korumayı zorlaştıran kırılgan kod yapıları oluşturdu ve std::variant tür jimnastiği kullanmadan heterojen konteynerlerin kullanılmasını engelledi. Ayrıca, CRTP, tüm türev sınıfların şablonlar haline getirilmesini gerektiriyordu ve bu, yüzlerce finansal araç tipi üzerinden şablonları meydana getirirken derleme sürelerini ve hata mesajı karmaşıklığını önemli ölçüde artırdı.
İkinci yaklaşım, çalışma zamanı polimorfizmi hata ayıklama için korurken constexpr-uyumlu arama tabloları üretecek büyük anahtar ifadeleri kapsayan Python betikleri kullanmayı önerdi. Bu yöntem, geliştiricilerin yeni finansal ürünler eklediklerinde kodu manuel olarak yeniden üretmelerini gerektiren kırılgan bir yapı oluşturarak, yine de yavaşlatma döngüleri ve betik şablonları ile gerçek C++ sınıf tanımlamaları arasındaki potansiyel senkronizasyon hataları tanıttı. Ayrıca, kod üreticisinin bakımını sağlamak özel bir beceri haline geldi ve yeni mühendislerin işe alımını önemli ölçüde zorlaştırdı.
Üçüncü yaklaşım, program başlangıcında bir kez hesaplanan değerleri saklayan tembel başlatma ile çalışma zamanı önbelleği önerdi. Bu strateji, temiz sanal kalıtım yapılarının korunmasını sağladı ve yeni nesne türlerinin dinamik olarak yüklenmesine olanak tanıdı, ancak gömülü sistemlerde gerçek ROM depolama gereksinimini ihlal etti ve çoklu iş parçacıklı ticaret ortamlarında başlatma sırasında yarış koşulları tanıttı. Ayrıca başlatma gecikmesi, yüksek frekanslı ticaret senaryolarında milisaniyenin altındaki başlatma sürelerinin zorunlu olduğu durumlarda kabul edilemez hale geldi.
Firma, sonuç olarak C++20'ye geçiş yapmayı ve constexpr sanal fonksiyonları kullanmayı, mevcut zarif kalıtım hiyerarşisini koruyarak kritik hesaplama yöntemlerini constexpr olarak işaretlemeyi seçti. Bu seçim, kod üretim betikleri ve şablon metaprogramlamanın teknik borçlarını ortadan kaldırdığı için öncelik haline geldi ve değerleri yalnızca okunan bellek segmentlerine önceden hesaplama yeteneğini kaybetmeden yapmayı sağladı. Göç, mevcut sanal yöntemlere constexpr belirteçlerinin eklenmesi gibi yalnızca minimal sözdizimsel değişiklikler gerektiriyordu, bu da geçişi mimari yeniden yazmalara göre düşük riskli hale getiriyordu.
Sonuç olarak, fiyatlandırma motoru için kod karmaşıklığında yüzde ellilik bir azalma sağlandı, risk tablolarının donanım yazılımlarına başarılı bir şekilde derlenmesi ve çalışma zamanı başlatma maliyetinin ortadan kaldırılması gerçekleşti. Mühendisler artık constexpr bağlamlarında statik yapılandırma için standart std::vector ve polimorfik işaretçileri kullanabiliyor, kod okunabilirliğini artırıyordu. Son olarak, sistem, tam tür güvenliğini korurken piyasa veri işleme için alt-mikrosaniye yanıt süreleri sağladı ve karmaşık metaprogramlama şablonlarının kaldırılmasıyla ikili boyutu on iki kilobyte azalttı.
Neden C++20 standardı, new aracılığıyla constexpr tahsise izin vermekte ancak sabit ifadelere karşılık gelen delete işlemini yasaklamaktadır, özellikle sanal yıkıcıların yer aldığı durumlarda?**
Asimetri, C++20'de ::operator new'nin constexpr-capable olarak belirtilmiş olmasından kaynaklanmaktadır, bu da derleyicinin çeviri sırasında soyut bir tampon üzerinden bellek alımını simüle etmesine olanak tanırken, ::operator delete çalışma zamanı sistemi ve potansiyel küresel durum değişikliği ile içsel olarak bağlantılı kalmaktadır. Polimorfik türlerle çalışırken, delete ifadesinin doğru temizliği sağlamak ve daha sonra depolamayı serbest bırakmak için sanal yıkıcıyı çağırması gerekir, ancak boşaltma işlevi constexpr değildir. Adaylar sıklıkla sabit değerlendirmenin, soyut makine içinde belirleyici, geri döndürülebilir işlemler gerektirdiğini, oysa bellek boşaltımının, tüm platform uygulamaları arasında constexpr-güvenli olamayacak kaynak serbest bırakmayı ima ettiğini gözden kaçırıyorlar.
Derleyici, çalışma zamanı vtable işaretçilerini kullanmadan sabit değerlendirme sırasında sanal fonksiyon çağrılarını nasıl çözümler?
Sabit değerlendirme sırasında, C++ derleyicisi programın bir soyut yorumunu oluşturur; burada nesne türleri, değerlerle birlikte meta veri olarak izlenir ve dinamik türlerin derleme zamanı yığını oluşturulur. Bir sanal fonksiyon çağrıldığında, derleyici bu meta veri karşısında ad araması yapar; bu da vtable işaretçisini dereference etmeden, doğru geçişi doğrudan ara temsile yerleştirmesine olanak tanır. Bu mekanizma, constexpr sanal dağıtımın derleme sırasında gerçek vtable depolama veya işaretçi kovalamayı gerektirmediği anlamına gelir, yine de vtable'lar çalışma zamanı kullanımı için üretilir; adaylar genellikle çalışma zamanı nesne düzenini, sabit ifade değerlendirmesi için kullanılan soyut makine ile karıştırıyorlar.
Nesne ömrü sona erdiğinde, neden bir constexpr sanal yıkıcının polimorfik temel sınıf işaretçisinin silinmesinin sabit bir ifadede geçerli olmasını engelleyen belirli kısıtlama nedir, yıkıcı gövdesi boş olsa bile?
Kısıtlama, yıkıcı tamamlandıktan sonra ::operator delete'yi çağırmak üzere tanımlanan delete ifadesinden kaynaklanmaktadır ve bu küresel boşaltma işlevi standart kütüphanede constexpr olarak tanımlanmamıştır. Yıkıcı basit ve constexpr niteliğinde olsa bile, delete ifadesi hem yıkım hem de boşaltım işlemi olarak tek bir işlem kapsamındadır. Boşaltım, belleği işletim sistemine veya yığın yöneticisine geri döndürebilmek için çalışma zamanı desteği gerektirdiğinden ve sabit değerlendirme çeviri birimleri arasında kalıcı bir yığın varlığını varsayamaz, işlem doğal olarak non-constexpr'dır. Yeni başlayanlar, bir yıkıcıyı constexpr olarak işaretlemenin otomatik olarak delete'in geçerli hale geleceğini varsayarak, nesne ömrü sona erme ile depolama dönüşümü arasındaki ayrımı gözden kaçırıyorlar.