Sorunun cevabı.
Sorunun geçmişi
C++23'ten önce, statik çok biçimliliği uygulamak için Garip Sürekli Şablon Deseni (CRTP) gerekiyordu. Bu yaklaşım, türetilmiş sınıfların, türetilmiş türle örneklenmiş bir temel sınıf şablonundan miras almasını zorunlu kıldı. İşlevsel olmasına rağmen, CRTP sesli kod ve bakımını zorlaştıran karmaşık miras hiyerarşileri üretti.
Sorun
Temel sorun, CRTP tabanlarındaki üye işlevlerin gerçek türetilmiş türü açık şablon parametreleri olmadan çıkarım yapamamasıydı. Bu kısıtlama, geliştiricilerin this nesnesini manuel olarak türetilmiş türe dönüştürmesini zorunlu kıldı ve miras zincirleri değiştiğinde kırılgan kod yarattı. Ayrıca, CRTP kolay yeniden yapılandırmayı engelledi ve şablon meta programlamaya dair bilgisi olmayan kullanıcılar için arayüzleri daha az sezgisel hale getirdi.
Çözüm
C++23, üye işlevlerin this parametresini açık bir parametre olarak belirlemesine olanak tanıyan açık nesne parametresi (bu, this çıkarımı) tanıttı. void func(this auto&& self) şeklinde yazıldığında, işlev herhangi bir nesne türünü kabul ederek miras yerine aşırı yükleme yoluyla statik çok biçimliliği sağlar. Bu yaklaşım, CRTP‘yi tamamen ortadan kaldırarak açık çok biçimliliği destekleyen daha temiz kod üretir.
// C++23 Yaklaşımı struct Vector { float x, y; template<typename Self> auto magnitude(this Self&& self) { return std::sqrt(self.x * self.x + self.y * self.y); } }; // Kullanım inheritance olmadan çalışır Vector v{3.0f, 4.0f}; float len = v.magnitude();
Hayattan bir durum
Bir oyun motoru ekibi, hem CPU hem de GPU derleme yollarını destekleyen bir matematiksel vektör kütüphanesine ihtiyaç duydu. Kütüphane, magnitude() ve normalize() gibi genel işlemler gerektirdi ve float, double ve half hassasiyet türleri arasında sıfır aşırı yüklenme ile çalıştı.
İlk olarak düşünülen yaklaşım, VectorBase<Derived, T> adında bir temel sınıfla CRTP idi. Bu, derleme zamanı çok biçimliliği sağladı ancak önemli karmaşıklıklar getirdi. Her yeni vektör türü, temel sınıftan miras alınmayı ve kendisini şablon parametresi olarak geçmeyi gerektiriyordu, bu da sesli koda ve yeniden yapılandırma sırasında karmaşık şablon örnekleme hatalarına neden oldu. Bakım zordu çünkü temel arayüz değiştiğinde, tüm türetilmiş sınıfların güncellenmesi gerekiyordu.
Düşünülen ikinci yaklaşım ise, mirası kaçınan serbest işlevler ve etiket dağıtımı ile işlev aşırı yüklemesiydi. Bu, miras almayı önledi fakat grafik ekibinin tercih ettiği nesne yönelimli tasarımı bozdu. Vektör örneklerini parametre olarak geçmeyi gerektirdi ve bu da matematiksel nesneler için doğal hissettirmedi. Ayrıca, API yüzeyini karmaşıklaştırdı ve yöntem zincirlemesini imkansız hale getirdi.
Seçilen çözüm, C++23’ün açık nesne parametre sözdizimiydi. Ekip, auto&& self parametrelerini kullanacak şekilde vektör sınıflarını yeniden yazdı ve miras olmadan statik çok biçimliliği sağladı. Bu yaklaşım, vec.magnitude() sözdizimini sezgisel olarak korurken, genel programlamayı destekleyerek şablon yığınını ortadan kaldırdı.
Sonuç olarak, şablonla ilgili derleme hatalarında %40 oranında bir azalma ve geliştirici üretkenliğinde bir artış sağlandı. Kod tabanı önemli ölçüde daha kolay bakım yapılabilir hale geldi ve yöntem zincirlemesi tüm vektör türlerinde sorunsuz çalıştı. Ekip, kütüphaneyi hem CPU hem de GPU hedeflerine başarıyla dağıttı ve CRTP karmaşıklığını ortadan kaldırdı.
Adayların genellikle gözden kaçırdığı noktalar
Neden açık nesne parametre çıkarımı, üye işlev const olarak tanımlandığında fakat çıkarılan tür const ile nitelendirilmemiş olduğunda başarısız olur?
Adaylar genellikle this auto&& self kullanırken, çıkarılan türün ifadenin cv-niteliklerini içerdiğini gözden kaçırırlar. Bir işlev const bir nesne üzerinde çağrıldığında, tür otomatik olarak const T& olarak çıkarılır.
Ancak aday, sabit bir nesne üzerinde parametreyi this T self (değerle) tanımlarsa, kopyalamaya çalışır. Bu, silinmiş bir kopya yapıcıyı tetikleyebilir veya pahalı derin kopya işlemleri başlatabilir.
Ana içgörü, auto&&’in referans çökme kurallarını takip etmesi ve sabitliği otomatik olarak korumasıdır. Bu, genel üye işlevleri için tercih edilen form haline getirir ve açık nitelendirme olmaksızın sabitliği sağlar.
Açık nesne parametresi, std::function aşırı yükü olmadan özyinelemeli lambda desenlerini nasıl sağlar?
Adaylar sık sık açık nesne parametrelerinin, lambdaların kendilerini std::function tür silme olmadan çağırmalarına izin verdiğini gözden kaçırırlar. Kendi kendisini kabul eden açık bir auto parametre ile lambda tanımlanarak, o parametreyi kullanarak özyineleme yapılabilir.
Örneğin, auto factorial = [](this auto&& self, int n) -> int { return n <= 1 ? 1 : n * self(n-1); }; ifadesi, hiç aşırı yük olmadan özyinelemeli bir lambda oluşturur. Derleyici, derleme zamanında tam türü bildiğinden, tam iç içe uygulama ve optimizasyonu sağlar.
Bu özellik olmadan, özyineleme std::function’ı gerektirir, bu da tür silme aşırı yükü getirir ve iç içe uygulamayı engeller. Alternatif olarak, geliştiriciler, niyetini bulanıklaştıran karmaşık sözdizimlerine sahip sabit nokta birleştiricileri kullanır.
Açık nesne parametresi, tam tür korumasıyla doğrudan öz başvuru sağlar. Bu desen, zarif özyinelemeli algoritmaları genel kodda desteklerken performansı korur.
Neden açık nesne parametreleri, geleneksel sınıf hiyerarşilerinin oluşumunu engellerken hala çok biçimli davranışı sağlar?
Bu ince nokta birçok adayı kafa karıştırır. Geleneksel çok biçimlilik, miras ve sanal işlevlere dayanır ve temel ve türetilmiş sınıflar arasında sıkı bir bağ kurar.
Açık nesne parametreleri, herhangi bir türün gereken arayüzü sağladığı yerde "açık çok biçimlilik" sağlar. Ortak bir temel sınıf veya sanal yok edicilerden miras alma zorunluluğu yoktur.
Ana ayrım, açık nesne parametreleri ile çok biçimliliğin, aşırı yükleme çözümü aracılığıyla derleme zamanında çözümlenmesidir. Dönüştürmesi gereken bir temel sınıf türü yoktur, bu da nesne dilimleme önler ve vtable aşırı yükünü ortadan kaldırır.
Ancak bu, heterojen nesneleri temel sınıf işaretçileri içeren bir kapsayıcıda saklamayı engeller; tür silme olmadan. Çok biçimlilik, katı bir statik yapıdır ve performans avantajları sunarken, dinamik çok biçimlilikten farklı mimari kısıtlamalar getirir.