C++98 boyunca, üye işlevler, gizli bir this işaretçisi aracılığıyla örtük nesneye erişiyordu; bu da const ve non-const bağlamlarını yönetmek için ayrı aşırı yüklemeler gerektiriyordu. C++11, lvalue ve rvalue nesneleri ayırt etmek için referans niteliklerini tanıttı. Bu, tüm cv-ref kombinasyonlarını kapsayacak şekilde her işlev için dört aşırı yükleme gerektirebilir ve bu da genel kütüphaneler için önemli kod tekrarı ve bakım yükleri oluşturuyordu.
Temel sorun, bir üye işlevin nesneyi arayan ile aynı değer kategorisi ve cv-qualifikasyonu ile döndürmesi gerektiğinde ortaya çıkar; bu da etkili hareket semantiği sağlamak veya sallanan referansları önlemek için gereklidir. Nesnenin türünü belirlemeden, geliştiriciler verbose aşırı yükleme setleri yazmak zorunda kaldılar veya kopyalama semantiği konusunda taviz vermek zorunda kaldılar; bu da etkili bir şekilde rvalue işlemini halletmede veya nesne referanslarını yayma ile ilgili ince yaşam döngüsü hatalarına yol açtı.
C++23, void foo(this auto&& self) şeklinde bir söz dizimi sunarak açık nesne parametrelerini tanıtıyor. Burada self, nesnenin değer kategorisini ve cv-niteliklerini yakalayan çıkarılan bir parametre haline gelir, böylece ayrı & ve && aşırı yüklemelere ihtiyaç kalmaz; çünkü std::forward<decltype(self)>(self), doğru kategoriyi yayar. Ancak, statik üye işlevler tam anlamıyla bir örtük nesneye sahip olmadığından, bu sözdizimini onlara uygulamak self'e bağlı bir nesne olma gereksinimini ihlal eder ve bu nedenle standartlara göre program hatalı duruma gelir.
// Pre-C++23: Dört aşırı yükleme gerekli class Builder { public: Builder& setName(...) & { /* ... */ return *this; } Builder const& setName(...) const& { /* ... */ return *this; } Builder&& setName(...) && { /* ... */ return std::move(*this); } Builder const&& setName(...) const&& { /* ... */ return std::move(*this); } }; // C++23: Tek aşırı yükleme class Builder { public: template<typename Self> auto setName(this Self&& self, ...) -> Self&& { // ... return std::forward<Self>(self); } };
Ekibimiz, DOM düğümlerinin ağaç oluşturma için yöntem zincirlemesini desteklediği yüksek performanslı bir JSON kütüphanesi geliştirdi; bu da Node sınıfının, farklı döndürme semantiklerine sahip addChild() yöntemlerini sağlamasını gerektiriyordu. Bu yöntemler, eğer ebeveyn bir lvalue ise referansla, eğer ebeveyn bir rvalue geçici ise değerle ebeveyni döndürmeleri gerekiyordu; bu da hareket kesintisini sağlar ve sona erecek nesnelerin yanlışlıkla değiştirilmesini önlerdi.
İlk uygulama, geleneksel referans nitelikli aşırı yüklemeleri kullandı. addChild'in dört versiyonunu sürdürdük: biri lvalue'lar için Node& döndüren, biri const lvalue'lar için Node const& döndüren, biri rvalue'lar için Node&& döndüren ve biri const rvalue'lar için Node const&& döndüren. Bu yaklaşım, performans gereksinimlerini karşılıyordu ama test yüzeyimizi dört kat artırdı ve const&& aşırı yüklemesinin & aşırı yüklemesinden bir kopyala-yapıştır hatası nedeniyle yanlışlıkla sallanan bir referans döndürmesiyle kritik bir hata ortaya çıktı.
Tamamen referans niteliklerini terk etmeyi ve her zaman değerle döndürmeyi düşündük, kopyaları optimize etmek için RVO'ya güvenerek, ancak bu, adlandırılmış nesneler üzerinde gereksiz hareketler zorladı ve döndürülen düğüme yönelik referansları saklayan mevcut kodla API uyumluluğunu bozdu. CRTP'yi, türetilmiş türü çıkaran bir temel sınıf şablonu ile değerlendirdik ama bu, kullanıcılar için uygulama ayrıntılarını açığa çıkardı ve kalıtım hiyerarşilerini karmaşıklaştırdı, ayrıca değer kategorisi yayılma sorununu tam olarak çözmedi.
C++23 açık nesne parametrelerinin benimsenmesi, aşırı yükleme kümesini tek bir şablon yöntemine düşürmemizi sağladı: template<typename Self> auto addChild(this Self&& self, ...) -> Self. Bu, gerekli olan tam değer kategorisini yakaladı, std::move veya std::forward tekrarı olmadan mükemmel yönlendirme sağladı ve yöntemin döngüsel karmaşıklığını bir yola düşürdü. Sonuç olarak, kodun %75 oranında bir azalma sağlandı ve aşırı yükleme ayrışması ile ilgili hata kategorisinin ortadan kaldırılması sağlandı.
Neden açık nesne parametre sözdiziminin işlevin geleneksel cv-niteliklerinin veya referans niteliklerinin parametre listesinin sonuna eklenmesini önlüyor?
Geleneksel üye işlevler, örtük this işaretçisi türünü değiştirmek için cv-nitelikleri ve referans niteliklerini parametre listesinin ardından koyar. Açık nesne parametreleri ile this Self&& self zaten Self'in tür çıkartımında cv-qualifikasyonu ve referans kategorisini kodlar. Ekstra nitelikler eklemek (const veya & gibi) mevcut olmayan bir örtük nesneyi nitelendirmeye çalışır ve bu da bir tür sistemi çelişkisi yaratır. Standart, bu birleşimi açıkça yasaklar çünkü açık parametre, hem parametre hem de niteliklerin rolünü üstlenir ve her iki durumun olmasına izin vermek, hangi semantiklerin çağrıyı yönlendirdiğine dair belirsizlik yaratır.
Açık nesne parametreleri kullanıldığında, işlev gövdesinde ad arama nasıl farklılık gösterir?
Geleneksel üye işlevlerde, niteliksiz adı arama otomatik olarak sınıf kapsamını arar; sanki this-> ile öncelik verilmiş gibi. Açık nesne parametrelerinde ise örtük bir this işaretçisi yoktur; self parametresi, üyelere erişmek için açıkça kullanılmalıdır. Adaylar genellikle void foo(this auto& self) içindeki memberın otomatik olarak this->member olarak çözüleceğini varsayar ama aslında self. nitelendirmesi veya sınıf nitelendirmesi olan ClassName::member gibi açık bir çağrı gerektirir. Bu, temel arama kurallarını değiştirir ve kodu taşırken, özellikle türetilmiş sınıflardan korunan üyelere erişim sağlarken uyum sağlanmasını gerektirir; burada self. açıkça çıkarılan türle karşı erişim kontrolünü tetikler.
Açık nesne parametreleri sanal işlev örtme işlemine katılabilir mi ve örtme ilişkisi üzerindeki kısıtlamalar nelerdir?
Açık nesne parametreleri sanal işlevlerde görünebilir, ancak örtme eşleşme kurallarını temelden değiştirir. Üst sınıf, virtual void bar(this Base& self) olarak tanımlandığında, türetilmiş sınıf, void bar(this Derived& self) olarak bildirdiğinde, geleneksel örtmelerin korunmuş dönüş türlerine izin vermesine rağmen, bu geçerli bir örtme oluşturmaz. Açık nesne parametresi örtme eşleşme amaçları için işlevin imzasının bir parçası haline gelir. Base& ve Derived& farklı türler olduğundan, bu geçerli bir örtme oluşturmaz. Bu, açık nesne parametrelerini kullanarak "sfinae-dostu" sanal işlevler veya çok biçimliliğin hiyerarşilerindeki tür koruyucu yöntem zincirini elde etme sık kullanılan desenini engeller. Örtme işlemi için, türetilmiş işlevin, temel işlevin açık parametre türüyle tam olarak eşleşmesi gerekir ve bu da o parametrenin örtme bağlamındaki çıkarım faydalarını ortadan kaldırır.