Soruya verilen cevap.
Sorunun tarihi
C++20'den önce, C++ derleme modeli, ön işleme direktifleri aracılığıyla metinsel dahil etmeye dayanıyordu. Bir başlık dosyası eklendiğinde, ön işlemci bu başlığın metnini gerçekten ekleyen dosyaya kopyalıyordu. Bu mekanizma, başlık dosyalarında tanımlanan makroların, bunları içeren her çeviri biriminin küresel isim alanına sızmasına neden olarak, tespit edilmesi zor ince hatalar ve isim çakışmalarına yol açıyordu.
Sorun
Makro sızması, büyük kod tabanlarında bakım sorunlarına yol açıyordu. Üçüncü taraf bir kütüphanede tanımlanan bir makro, tüketici kodundaki anahtar kelimeleri veya yaygın tanımlayıcıları sessizce yeniden tanımlayabiliyordu, bu da derleme hatalarına veya aslında neden ile ilgisi olmayan çalışma zamanı hatalarına neden oluyordu. Geleneksel geçici çözümler, #undef korumaları gibi, manuel, hata yapmaya açık ve karmaşık bağımlılık grafiklerinde ölçeklenemezdi. Temel sorun, ön işlemcinin kapsam veya arayüz sınırları kavramına sahip olmamasıydı.
Çözüm
C++20 modülleri, dil düzeyinde çalışan bir anlamsal ithalat mekanizması tanıtır. import module_name; ile bir modül ithalatında, derleyici ithalat çeviri biriminin ön işleme direktiflerini çalıştırmadan 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ıkça o modülün uygulamasına özel kalır. Bu özellik, makroların çeviri birimi sınırlarını aşmasını engelleyerek 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ızdıralmaz 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ÜNMEZ // #ifdef INTERNAL_CALC_FACTOR yanlış olur int main() { double result = compute(10.0); // Güzel çalışıyor }
Hayattan bir durum
Bir finansal ticaret firması, yüzlerce modül arasında milyonlarca satır kod içeren büyük bir kod tabanı sürdürüyordu. Kamu başlık dosyalarında MIN ve MAX gibi makrolar tanımlayan bir eski matematik kütüphanesine güveniyorlardı. Bu makrolar, min ve max'ı değişken adları veya işlev şablonları olarak kullanan standart kütüphane işlevleri ve üçüncü taraf JSON ayrıştırma kütüphaneleri ile sık sık çakışıyordu.
İlk düşünülmüş yaklaşım, tüm üçüncü taraf başlıklarını #pragma once tarzı korumalarla sarmalamak ve her dahil etmeden sonra sorunlu makroları manuel olarak #undef yapmak oldu. Bu, geliştiricilerin hangi başlıkların hangi makroları tanımladığını hatırlamasını ve her ekleme sonrasında temizlemesini gerektiriyordu. Bu yaklaşım, tek bir #undef'in eksik kalmasının, kod tabanın ilişkili olmayan parçalarında hatalara neden olabileceği için kırılgandı. Ayrıca, aynı başlık metninin çeviri birimleri arasında tekrar tekrar işlenmesi nedeniyle derleme sürelerini önemli ölçüde artırıyordu.
Düşünülen ikinci yaklaşım, matematik kütüphanesini makrolar yerine çevrimiçi işlevler ve şablonlar kullanacak şekilde dönüştürmekti. Bu, sızıntı sorununu çözse de, eski kütüphanede kapsamlı değişiklikler gerektiriyordu. Matematik kütüphanesi birden fazla ekip tarafından kullanılıyor ve değiştirilmesi, belirli makro değerlendirme mantıkları veya yan etkileriyle ilgili mevcut hesaplamaları bozma riski taşıyordu. Yeniden yapılandırma çabaları altı ay sürmesi öngörülüyordu ve ticaret platformu için çok riskli olduğu kabul edildi.
Seçilen çözüm, C++20 modüllerine geçişti. Ekip, matematik kütüphanesini, makroları modül uygulamasının içinde tutarken matematiksel işlevleri dışa aktaran bir modül haline dönüştürdü. #include <mathlib.h> yerine import mathlib; kullanarak, tüketici çeviri birimleri artık MIN ve MAX makrolarını görmüyordu. Bu yaklaşım, yalnızca dışa aktarma ifadeleri eklemeyi ve başlıkları modül arayüzü birimleri olarak dönüştürmeyi gerektiriyordu. Geçiş iki hafta sürdü, altı ay yerine. Sonuç, kod tabanındaki makro ile ilgili isim çakışmalarının ortadan kaldırılması ve modülün derlenmiş arayüzü sayesinde derleme sürelerinde %15'lik bir azalmayla sonuçlandı.
Adayların sık sık gözden kaçırdığı şeyler
Modül arayüzü biriminin derlenmiş ikili formatının, makro sızıntısını metinsel başlık dahil etme ile karşılaştırıldığında nasıl engellediği?
Adaylar çoğunlukla C++20 modüllerinin, modülün dışa aktarılan arayüzünün ikili temsilleri olan derlenmiş modül arayüzü birimleri (CMI) ürettiğini gözden kaçırırlar. Ön işlemci tarafından işlenmeyen ve makro tanımları olarak metin içermeyen metinsel başlıkların aksine, CMI'ler dışa aktarılan işlevler, türler ve şablonlar hakkında anlamsal bilgi depolar.
Ön işlemci, ithal edilen modülün içeriğini işlemiyor; yalnızca ithalat beyanını görüyor. Bu nedenle, modülün uygulamasında veya hatta arayüz biriminde tanımlanan makrolar, ithalatı yapan kişiye görünmez. Bu, #include'dan temelde farklıdır, çünkü #include, metni, #define direktiflerini de dahil olmak üzere, harfi harfine kopyalar.
Bunu anlamak, modüllerin metin dahil etme modelinden anlamsal ithalat modeline geçiş yaptığını kabul etmeyi gerektirir. İkili format, yalnızca açıkça dışa aktarılan varlıkların görünür olmasını sağlar ve makrolar, yalnızca makro direktifleri kullanılarak özel olarak dışa aktarılmadığı sürece, dışa aktarılan arayüzün bir parçası değildir.
Neden dışa aktarılan bir modülden makrolar, #include direktiflerinden farklı davranır?
Adaylar genellikle makroların export import ile dışa aktarılmasını, normal makro davranışıyla karıştırıyor. C++20 modülleri, makroları export import ile dışa aktarmaya izin verse de, bu makrolar yalnızca modülü ithal eden kodları etkiler ve bu ithalat kapsamının ötesine sızmaz.
#include'dan farklı olarak, makrolar, açıkça tanımlanmadıkça veya dosyanın sonuna kadar çeviri biriminde varlığını sürdürüyor, modüllerden dışa aktarılan makrolar, ithal eden çeviri biriminin o modül ile olan ifşasına sıkıdır. Ön işlemci, ithal edilen makroları, ithalat noktasında tanımlanmış gibi işler, ancak bunlar subsequent (sonraki) ithalatları veya küresel ön işlemci durumunu metinsel dahil etme ile aynı şekilde etkilemez.
Dahası, eğer birden fazla modül çelişen makrolar dışa aktarırsa, çelişki, ithalat sırasında tespit edilir ve derleme sırasında sessiz yeniden tanımlama hatalarına neden olmaz. Bu kapsam davranışı, metinsel dahil etmenin eksik olduğu hijyeni sağlar ve makroların, düzgün bir ad alanı kapsamlı varlıklar gibi davranmasını sağlar.
Modülün ön işlemciden bağımsız olması, derleme sistemi entegrasyonu ve bağımlılık taramasını nasıl etkiler?
Adaylar genellikle C++20 modüllerinin, başlıkların derleme sırasında bağımlılıklarının keşfedildiği, derleme sistemlerinin modül bağımlılıklarını anlamasını gerektirdiğini gözden kaçırırlar. Modüller metin dosyaları yerine derlenmiş birimleri olduğundan, derleme sistemi, dışa aktardıklarını ve ithal ettiklerini belirlemek için modül arayüzü birimlerini taramak zorundadır.
Bu, iki aşamalı bir derleme süreci gerektirir: ilk olarak, modül arayüzü birimlerini taramak ve bir bağımlılık grafiği oluşturmak, ardından bağımlılık sırasına göre derlemek. Ön işlemciden 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ı kurulumu sınırlıdır. Derleme sistemleri, yalnızca kaynak dosyaları değil, derlenmiş modül eserlerini (BMI - İkili Modül Arayüzü) takip etmelidir.
Bu, bağımlılık izleme ve artımlı derlemelerin nasıl çalıştığını köklü bir şekilde değiştirir. Derleme sistemi, şimdi kendi bağımlılık zincirlerine sahip ara eserler olarak BMI dosyalarını yönetmelidir. Bu, CMake veya Bazel gibi derleme araçlarının modül bilincine sahip derleme grafiklerini desteklemesi için güncellenmesini gerektirir.