Tarihçe: C++20'den önce, geliştiriciler kaynak kodu meta verilerini kaydetmek ve hata ayıklamak için __FILE__ ve __LINE__ gibi ön işleyici makrolarına güveniyorlardı. Bu makrolar, genişletme bağlamı sorunları, isim alanı kirliliği ve soyutlama katmanları aracılığıyla yayılma yeteneğinin olmaması gibi dezavantajlara sahipti. C++20 standardı, otomatik olarak çağrı yeri bilgisini yakalayan, tür güvenli, constexpr uyumlu bir alternatif sağlamak için std::source_location'ı tanıttı.
Sorun: Kaydetme işlevselliğini yardımcı işlevlerde sarmaladığında, makro tabanlı yaklaşımlar, sarmalayıcı tanımının yerini değil, gerçek çağrı yerini yakalayarak derin çağrı yığınlarındaki hataları noktasal düzeyde belirlemeyi imkansız hale getirir. Ayrıca, kaynak meta verisinin her işlev imzası aracılığıyla elle yayılması, müdahaleci API değişiklikleri ve bakım yükleri yaratmaktadır. Kesin parametre geçişi olmadan çağrı noktasında dosya adı, satır numarası, sütun ve işlev adı gibi bilgileri yakalayan bir mekanizmaya ihtiyaç vardı.
Çözüm: std::source_location, sadece derleyici tarafından current() statik üye işlevi aracılığıyla örneklendirilebilen, özel bir yapıcıya sahip, basit bir kopyalanabilir yapıdadır. Bir işlev parametresine varsayılan argüman olarak kullanıldığında, std::source_location::current() çağrı yerinde değerlendirilir, alanlarını kesin kaynak koordinatlarıyla doldurmak için derleyici iç mekanizmalarını kullanır. Bu tasarım, rastgele kaynak konumlarının manuel olarak oluşturulmasını önler, tanılayıcı bütünlüğünü sağlar ve şablon örnekleme ve geri çağırma zincirleri aracılığıyla sorunsuz yayılmayı mümkün kılar.
#include <source_location> #include <iostream> #include <string> class Logger { public: static void log(const std::string& message, std::source_location loc = std::source_location::current()) { std::cout << loc.file_name() << ":" << loc.line() << " [" << loc.function_name() << "] " << message << std::endl; } }; void process_data(int value) { if (value < 0) { Logger::log("Geçersiz değer alındı"); // Bu satırı yakalar, Logger::log tanımını değil } }
Bağlam: Yüksek frekanslı bir ticaret sistemi, hataların kesin köken satırını milyonlarca satırlık kod arasında belirlemek zorunda olduğu dağıtılmış kaydı gerektiriyordu, bu, şablonlu algoritmalar ve lambda geri çağırmalarını da kapsıyor. Mevcut kod tabanı, __FILE__ ve __LINE__'i genişleten makro tabanlı LOG_ERROR() kullanıyordu, ancak bu, geliştiriciler validate_input() gibi yardımcı işlevler tanıttığında bozuldu, bu da tüm hataların yardımcı işlevin iç satırını rapor etmesine yol açtı, iş mantığı çağrı yerini değil.
Sorun: Makro genişlemesi, kaydetme çağrısının fiziksel olarak yazıldığı yeri değil, mantıksal hata yerini yakaladı. validate_input() 500 farklı yerden çağrıldığında, tüm 500 hata aynı dosya ve satırı doğrulama fonksiyonu içinde rapor etti. Bu, yarış durumu incelemeleri sırasında üretim hata ayıklamayı neredeyse imkansız hale getirdi.
Değerlendirilen Çözümler:
Seçenek 1: Açık Parametrelerle Makro Yayılımı. Her işlevin const char* file, int line parametrelerini kabul etmesini sağlamayı düşündük, bunları her çağrı yerinde enjekte eden bir değişken makro sarmalayıcısı aracılığıyla. Artıları: Rastgele çağrı derinlikleri boyunca doğru konum bilgisi saklar. Eksileri: Büyük API kirliliği, üçüncü taraf kütüphane arayüzlerini bozar, derleme sürelerini önemli ölçüde artırır ve makroların yasak olduğu constexpr bağlamlarında kullanımını engeller.
Seçenek 2: Hata Ayıklama Sembolleri ile Çalışma Zamanı Yığın Sarmalayıcı. Platforma özgü API'ler kullanarak backtrace() gibi çalışma zamanı yığın izini yakalamayı veya Windows üzerinde CaptureStackBackTrace kullanmayı düşünün, ardından hata ayıklama sembolleri kullanarak adresleri satır numaralarına çözün. Artıları: API'lere müdahaleci değil, tam çağrı yığınını yakalar. Eksileri: Aşırı çalışma zamanı yükü (yüksek frekanslı yollar için uygun değil), üretime hata ayıklama sembollerinin gönderilmesini gerektirir ve çözüm, çökme koşullarında eş zamanlı ve güvenilir değildir.
Seçenek 3: Varsayılan Argümanlarla std::source_location. Makro yerine std::source_location loc = std::source_location::current()'i son parametre olarak kabul eden bir işlev ile değiştirmeyi düşünün. Artıları: Sıfır çalışma zamanı yükü (constexpr yapımı), şablonlar aracılığıyla otomatik yayılım, kesin tanılama için sütun bilgisi yakalar ve isim alanı kapsamını kirlilik olmadan dikkate alır. Eksileri: C++20 derleyici desteği gerektirir ve geliştiricilerin bunu bir varsayılan argüman olarak yerleştirmeyi hatırlaması gerekir (işlev gövdesinin içinde değil, burada işlevin iç konumunu yakalayacak şekilde).
Seçilen Çözüm ve Sonuç: Seçenek 3'ü seçtik çünkü ticaret sistemi zaten C++20'ye geçiyordu ve std::source_location'ın constexpr doğası, kayıt format dize kontrolünün derleme zamanında doğrulanmasını sağlarken, nanosecond seviyesindeki performans gereksinimlerini korudu. Uygulamanın ardından, hata raporları trading_engine.cpp:847 [auto execute_order(const Order&)::(lambda)] gibi kesin satır numaraları içeriyordu ve bu, kritik bir yarış durumunu iki günde yerine iki saatte tanımlamamızı sağladı. std::source_location'ın manuel olarak oluşturulamaması kuralı, junior geliştiricilerin test sırasında yanlış yerler göndermesini önleyerek, üretim günlüklerinin adli olarak güvenilir kalmasını sağladı.
Neden std::source_location::current() varsayılan argüman olarak kullanıldığında özeldir ve eğer işlev gövdesinin içinde çağırırsanız ne olur?
std::source_location::current() bir varsayılan argüman olarak göründüğünde, C++20 standardı, derleyicinin bunu çağrı yerinde değerlendirmesini emreder ve işlevin çağrıldığı satırı yerleştirir. Eğer işlev gövdesinin içinde yer alırsa, bu, işlev tanımının içindeki belirli satırın yerini değerlendirir ve çağrı yeri atıfı için kullanışsız hale gelir. Bu davranış, bu belirli işlev için dil spesifikasyonundaki özel bir durumdur; normal varsayılan argümanlar tanım yerinde değerlendirilir, ancak std::source_location otomatik kaydı etkinleştirmek için bu özel muameleyi alır. Yeni başlayanlar genellikle auto loc = std::source_location::current(); ifadesini günlükleme fonksiyonlarının ilk satırı olarak koyar ve ardından her günlük kaydının aynı iç satıra işaret ettiğini merak ederler.
Rastgele dosya ve satır numaralarıyla std::source_location'u manuel olarak oluşturabilir misiniz ve standart bunu neden engelliyor?
Hayır, geçerli bir std::source_location'ı manuel olarak oluşturamazsınız çünkü yapıcıları özel olup yalnızca uygulama tarafından erişilebilir. Standart, tanılayıcı bilgilerin bütünlüğünü korumak için bu kısıtlamayı uygular ve geliştiricilerin güvenlik açısından kritik kayıt sistemlerinde kaynak konumlarını taklit etmelerini veya uydurmalarını önler. Birim testleri için günlük çıktıları simüle etmek isteyebilirsiniz, ancak standart komitesi adli güvenilirliği test esnekliğinden öncelikli hale getirmiştir. Bir örneği elde etmenin tek yolu current()'tür; bu, yapının özel alanlarını gerçek çeviri biriminin iç temsili ile dolduran bir derleyici iç mekaniği olarak uygulanmıştır.
std::source_location lambda ifadeleri, şablon örneklemeleri ve iç içe işlevlerde doğru bir şekilde çalışıyor mu ve hangi özel meta verileri yakalıyor?
Evet, std::source_location bu bağlamların hepsinde doğru çalışıyor, ancak adaylar genellikle ayrıntıları kaçırıyor. Lambdalar için function_name() uygulama tanımına dayanan bir ad döner (genellikle operator() veya lambdanın iç sembolü gibi), file_name() ve line() ise kaynağın lambda tanım yerini gösterir. Şablon örneklemelerinde, her farklı örnekleme, kullanılan özel şablon argümanlarına işaret eden kendi kaynak konumunu üretir. Yapı, dört parça meta veriyi yakalar: file_name() (const char*), line() (uint_least32_t), column() (uint_least32_t, genellikle tahmin edilenden az ama makro yoğun kod için kritik), ve function_name() (const char*). Çoğu aday column()'un, aynı fiziksel satır üzerindeki birden çok makro çağrısını ayırt ettiğini veya function_name()'ın çözülmüş semboller döneceğini varsaymaktadır (aslında uygulanmanın ham işlev imzasını döner).