C++ProgramlamaC++ Yazılım Mühendisi

C++20 modüllerinin çeviri birimi sınırları arasında makro sızıntısını ortadan kaldıran belirli özelliği nedir?

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

Sorunun cevabı.

Sorunun tarihi

C++20'den önce, C++ derleme modeli, ön işleyici direktifleri aracılığıyla metin dahil etmeye dayanıyordu. Bir başlık dosyası dahil edildiğinde, ön işlemci, o başlığın metnini dahil eden dosyaya harfi harfine kopyalıyordu. Bu mekanizma, başlıklarda tanımlanan makroların dahil eden her çeviri biriminin global ad alanına sızmasına neden oldu ve bu da teşhis edilmesi zor ince hatalar ve isim çakışmalarına yol açtı.

Sorun

Makro sızıntısı, büyük kod tabanlarında bakım kabusları yarattı. Üçüncü taraf bir kütüphanede tanımlanan bir makro, tüketici kodunda anahtar kelimeleri veya yaygın tanımlayıcıları sessizce tanımlayarak derleme hatalarına ya da görünüşte alakasız çalışma zamanı hatalarına neden olabiliyordu. Geleneksel çözümler, #undef korumaları gibi, manuel, hata yapmaya açık ve karmaşık bağımlılık grafiklerinde ölçeklenemeyen çözümlerdi. Temel sorun, ön işleyicinin kapsam veya arayüz sınırları kavramına sahip olmamasıydı.

Çözüm

C++20 modülleri, ön işleyici seviyesinden ziyade dil seviyesi üzerinde işleyen bir anlamsal ithalat mekanizması getiriyor. import module_name; ile bir modül ithal edildiğinde, derleyici, ithal eden çeviri biriminden ön işleyici direktiflerini yürütmeden modülün dışa aktarılan arayüzünü işler. Modül içinde tanımlanan makrolar, açıkça dışa aktarılmadığı sürece, o modülün uygulamasına özeldir. Bu özellik, makroların çeviri birimi sınırları arasında sızmamasını sağlar, gerçek kapsülleme sağlar ve isim kirliliğini önler.

// mathlib.cpp (Modül uygulaması) module; #define INTERNAL_CALC_FACTOR 3.14 // Özel makro, sızmaz export module mathlib; export double compute(double x) { return x * INTERNAL_CALC_FACTOR; } // main.cpp (Tüketici) import mathlib; // INTERNAL_CALC_FACTOR burada GÖRÜNÜM KAZANMAZ // #ifdef INTERNAL_CALC_FACTOR yanlış olur int main() { double result = compute(10.0); // Sorunsuz çalışır }

Gerçek hayat durumu

Bir finansal ticaret firması, yüzlerce modülde milyonlarca satırlık büyük bir kod tabanını sürdürüyordu. Kamu başlıklarında MIN ve MAX gibi makrolar tanımlayan eski bir matematik kütüphanesine güveniyorlardı. Bu makrolar, standart kütüphane fonksiyonları ve min ve max gibi değişken adlarını veya fonksiyon şablonlarını kullanan üçüncü taraf JSON ayrıştırıcı kütüphaneleriyle sıklıkla çakışıyordu.

İlk düşünülen yaklaşım, tüm üçüncü taraf başlıklarını #pragma once tarzı korumalarla sarmak ve her dahil edişten sonra sorunlu makroları manuel olarak #undef yapmaktı. Bu, geliştiricilerin hangi başlıkların hangi makroları tanımladığını hatırlamalarını ve her dahilde temizleme yapmalarını gerektiriyordu. Yaklaşım kırılgandı çünkü tek bir #undef işlemi unutmak, kod tabanının ilgisiz bölümlerinde hatalara yol açabilirdi. Ayrıca, ön işleyicinin aynı başlık metnini tekrar tekrar işlemeye devam etmesi nedeniyle derleme sürelerini önemli ölçüde artırıyordu.

İkinci yaklaşım, matematik kütüphanesini makrolar yerine iç içe işlevler ve şablonlar kullanacak şekilde dönüştürmekti. Bu sızıntı sorununu çözerken, eski kütüphaneyi kapsamlı bir şekilde değiştirmeyi gerektiriyordu. Matematik kütüphanesi birden fazla takım tarafından kullanılıyordu ve onu değiştirmek, belirli makro değerlendirme anlamları veya yan etkilerine bağımlı mevcut hesaplamaları bozma riski taşıyordu. Yeniden yapılandırma çabasının altı ay sürmesi bekleniyordu ve ticaret platformu için çok riskli olarak değerlendirildi.

Seçilen çözüm, C++20 modüllerine geçiş yapmak oldu. Ekip, matematik kütüphanesini, matematiksel işlevler dışa aktarırken makroları modül uygulamasına kapalı tutan bir modül haline dönüştürdü. #include <mathlib.h> yerine import mathlib; kullanarak, tüketen çeviri birimleri artık MIN ve MAX makrolarını görmemeye başladı. Bu yaklaşım, yalnızca dışa aktarma ifadeleri eklemeyi ve başlıkları modül arayüz birimlerine dönüştürmeyi gerektiriyordu. Geçiş iki hafta sürdü, altı ay yerine. Sonuç olarak, kod tabanında makro ile ilgili isim çakışmalarının ortadan kaldırılması ve modülün derlenmiş arayüzü sayesinde derleme sürelerinde %15 azalma sağlandı.

Adayların sıkça atladığı noktalar

Modül arayüz biriminin derlenmiş ikili formatı, makro sızıntısını metin başlık dahil etme ile karşılaştırıldığında nasıl önler?

Adaylar sıklıkla, C++20 modüllerinin, dışa aktarılan arayüzün ikili temsillerini içeren derlenmiş modül arayüz birimleri (CMI) ürettiğini gözden kaçırır. Metin başlıklarının ön işleyici tarafından işlenmesi ve makro tanımlamalarını metin olarak içermesi yerine, CMI'ler dışa aktarılan fonksiyonlar, türler ve şablonlar hakkında anlamsal bilgi saklar. Ön işleyici, ithal edilen modülün içeriğini işlemez; yalnızca ithalat bildirimini görür. Bu nedenle, modülün uygulamasında veya arayüz biriminde tanımlanan makrolar, ithalatçıya görünmez. Bu, #include ile temel bir farktır, çünkü #include ifadesi makro direktifleri dahil olmak üzere metni harfi harfine kopyalar. Bunu anlamak, modüllerin metin dahil etme modelinden anlamsal ithalat modeline geçiş yaptığını kabul etmeyi gerektirir.

Dışa aktarılan bir modül aracılığıyla export import edilen makrolar, #include direktiflerine göre neden farklı davranır?

Adaylar sıklıkla export import ile makroların normal makro davranışını karıştırırlar. C++20, makroları export import ile dışa aktarmaya izin verirken, bu makrolar yalnızca modülü ithal eden kodu etkiler ve o ithalat kapsamının dışına sızmaz. #include ile, makrolar, açıkça tanımlanmadıkları veya dosyanın sonuna kadar, çeviri biriminde kalmaya devam ederken, modüllerden dışa aktarılan makrolar, modülün o ithal eden çeviri biriminin maruziyetine göre sınırlıdır. Ayrıca, birden fazla modül çelişen makrolar dışa aktarırsa, çelişki, derleme sırasında sessiz tanım hatalarına neden olmak yerine, ithalat zamanında tespit edilir. Bu kapsam davranışı, metinsel dahil etmenin eksik olduğu hijyeni sağlar.

Modülün ön işleyiciden bağımsızlığı, yapım sistemi entegrasyonu ve bağımlılık taramasını nasıl etkiler?

Adaylar sıklıkla, C++20 modüllerinin yapım sistemlerinin derleme başlamadan önce modül bağımlılıklarını anlamasını gerektirdiğini gözden kaçırır, oysa başlıklar derleme sırasında keşfedilir. Modüller metin dosyaları yerine derlenmiş birimler oldukları için, yapım sisteminin modül arayüz birimlerini çözmesi ve hangi şeyleri dışa aktardığını ve hangi şeyleri ithal ettiğini belirlemesi gerekir. Bu, iki aşamalı bir yapı süreci gerektirir: ilk olarak, modül arayüz birimlerini taramak, böylece bir bağımlılık grafiği oluşturmak, ardından bağımlılık sırasına göre derlemek. Ön işleyiciden bağımsızlık, başlık dahil etme için geleneksel #ifdef korumalarının geçersiz olacağı anlamına gelir ve modül arayüzlerinin makro tabanlı yapılandırması sınırlıdır. Yapım sistemleri, yalnızca kaynak dosyalarını izlemek yerine derlenmiş modül artifaktlarını (BMI - İkili Modül Arayüzü) izlemelidir, bu da bağımlılık takibi ve artımlı derlemelerin çalışmasını temel olarak değiştirir.