27 Mart 2025
En başından beri görev, birkaç milyon satıra kadar kod içeren birkaç projenin taşınmasını içeriyordu. Esasen, çevirici için teknik şartname “tüm bunların C++'a taşınmasını ve doğru şekilde çalışmasını sağlama” ifadesine indirgenmişti. C++ ürünlerini yayınlamaktan sorumlu olanların işi, kodu çevirmeyi, testleri çalıştırmayı, sürüm paketlerini hazırlamayı vb. içerir. Karşılaşılan sorunlar genellikle birkaç kategoriden birine girer:
Bu listedeki ilerleme yukarıdan aşağıyadır — örneğin, çevrilen koddaki derleme sorunlarını çözmeden işlevselliğini ve performansını doğrulamak imkansızdır. Sonuç olarak, uzun süredir devam eden birçok sorun ancak CodePorting.Translator Cs2Cpp projesi üzerindeki çalışmaların sonraki aşamalarında keşfedildi.
Başlangıçta, nesneler arasındaki döngüsel bağımlılıklardan kaynaklanan basit bellek sızıntılarını düzeltirken, alanlara CppWeakPtr
niteliğini uyguladık, bu da WeakPtr
türünde alanlarla sonuçlandı. WeakPtr
, lock()
metodunu çağırarak veya örtük olarak (ki bu sözdizimsel olarak daha uygundur) SharedPtr
'a dönüştürülebildiği sürece bu durum sorun yaratmadı. Ancak, daha sonra CppWeakPtr
niteliği için özel bir sözdizimi kullanarak konteynerler içinde bulunan referansları da zayıf hale getirmek zorunda kaldık ve bu durum birkaç hoş olmayan sürprize yol açtı.
Benimsediğimiz yaklaşımla ilgili ilk sorun işareti, C++ perspektifinden bakıldığında, MyContainer<SharedPtr<MyClass>>
ve MyContainer<WeakPtr<MyClass>>
'ın iki farklı tür olmasıydı. Sonuç olarak, aynı değişkende saklanamazlar, aynı metoda geçirilemezler, ondan döndürülemezler vb. Başlangıçta yalnızca referansların nesne alanlarında nasıl saklanacağını yönetmek için tasarlanan nitelik, giderek daha garip bağlamlarda görünmeye başladı; dönüş değerlerini, argümanları, yerel değişkenleri vb. etkiledi. Bunu işlemekten sorumlu çevirici kodu her geçen gün daha karmaşık hale geldi.
İkinci sorun da beklemediğimiz bir şeydi. C# programcıları için, nesne başına tek bir ilişkisel koleksiyona sahip olmak doğal görünüyordu; bu koleksiyon hem mevcut nesnenin sahip olduğu ve başka türlü erişilemeyen nesnelere benzersiz referansları hem de üst nesnelere referansları içeriyordu. Bu, belirli dosya formatlarından okuma işlemlerini optimize etmek için yapılmıştı, ancak bizim için aynı koleksiyonun hem güçlü hem de zayıf referanslar içerebileceği anlamına geliyordu. İşaretçi türü, çalışma modunun nihai belirleyicisi olmaktan çıktı.
Açıkçası, bu iki sorun mevcut paradigma içinde çözülemedi ve işaretçi türleri yeniden gözden geçirildi. Bu revize edilmiş yaklaşımın sonucu, iki değerden birini kabul eden bir set_Mode()
metoduna sahip SmartPtr
sınıfı oldu: SmartPtrMode::Shared
ve SmartPtrMode::Weak
. Tüm SmartPtr
yapıcıları da aynı değerleri kabul eder. Sonuç olarak, her işaretçi örneği iki durumdan birinde bulunabilir:
Modlar arasında geçiş, çalışma zamanında ve herhangi bir anda gerçekleşebilir. Zayıf referans sayacı, nesneye en az bir zayıf referans olana kadar oluşturulmaz.
İşaretçimizin desteklediği özelliklerin tam listesi şöyledir:
intrusive_ptr
semantiği: aynı nesne için oluşturulan herhangi bir sayıda işaretçi tek bir referans sayacını paylaşacaktır.->
): işaret edilen nesneye erişim için.SmartPtr
sınıfı şablonludur (templated) ve sanal metotlar içermez. Referans sayacı depolamasını yöneten System::Object
sınıfıyla sıkı sıkıya bağlıdır ve yalnızca onun türetilmiş sınıflarıyla çalışır.
Tipik işaretçi davranışından sapmalar vardır:
Eski kodla uyumluluğu sürdürmek için SharedPtr
türü SmartPtr
için bir takma ad (alias) haline geldi. WeakPtr
sınıfı artık SmartPtr
'dan miras alır, alan eklemez ve yalnızca her zaman zayıf referanslar oluşturmak için yapıcıları geçersiz kılar.
Konteynerler artık her zaman MyContainer<SmartPtr<MyClass>>
semantiği ile taşınır ve saklanan referansların türü çalışma zamanında seçilir. STL veri yapılarına dayalı olarak manuel olarak yazılan konteynerler için (öncelikle System
ad alanındaki konteynerler), varsayılan referans türü özel bir ayırıcı (custom allocator) kullanılarak ayarlanır, ancak yine de tek tek konteyner öğeleri için modun değiştirilmesine izin verilir. Çevrilen konteynerler için, referans saklama modunu değiştirmek için gerekli kod çevirici tarafından oluşturulur.
Bu çözümün dezavantajları öncelikle işaretçi oluşturma, kopyalama ve silme işlemleri sırasında performansın düşmesini içerir, çünkü olağan referans sayımına referans türünün zorunlu bir kontrolü eklenir. Spesifik rakamlar büyük ölçüde test yapısına bağlıdır. İşaretçi türünün değişmeyeceğinin garanti edildiği yerlerde daha optimize kod üretme konusunda tartışmalar halen devam etmektedir.
Taşıma yöntemimiz, referansların nerede zayıf olması gerektiğini işaretlemek için kaynak C# koduna manuel olarak nitelikler yerleştirmeyi gerektirir. Bu niteliklerin doğru yerleştirilmediği kod, çeviriden sonra bellek sızıntılarına ve bazı durumlarda başka hatalara neden olacaktır. Niteliklere sahip kod şuna benzer:
struct S {
MyClass s; // Nesneye güçlü referans
[CppWeakPtr]
MyClass w; // Nesneye zayıf referans
MyContainer<MyClass> s_s; // Güçlü referanslar içeren bir konteynere güçlü referans
[CppWeakPtr]
MyContainer<MyClass> w_s; // Güçlü referanslar içeren bir konteynere zayıf referans
[CppWeakPtr(0)]
MyContainer<MyClass> s_w; // Zayıf referanslar içeren bir konteynere güçlü referans
[CppWeakPtr(1)]
Dictionary<MyClass, MyClass> s_s_w; // Anahtarların güçlü referanslarla, değerlerin ise zayıf referanslarla saklandığı bir konteynere güçlü referans
[CppWeakPtr, CppWeakPtr(0)]
Dictionary<MyClass, MyClass> w_w_s; // Anahtarların zayıf referanslarla, değerlerin ise güçlü referanslarla saklandığı bir konteynere zayıf referans
}
Bazı durumlarda, SmartPtr
sınıfının takma ad (aliasing) yapıcısını veya saklanan referans türünü ayarlayan metodunu manuel olarak çağırmak gerekir. Taşınan kodu düzenlemekten kaçınmaya çalışıyoruz, çünkü bu tür değişikliklerin her çevirici çalıştırıldıktan sonra yeniden uygulanması gerekir. Bunun yerine, bu tür kodları C# kaynağında tutmayı hedefliyoruz. Bunu başarmanın iki yolu vardır:
class Service {
public static void SetWeak<T>(T arg) {}
}
class Service {
public:
template <typename T> static void SetWeak(SmartPtr<T> &arg)
{
arg.set_Mode(SmartPtrMode::Weak);
}
};
class MyClass {
private Dictionary<string, object> data;
public void Add(string key, object value)
{
data.Add(key, value);
//CPPCODE: if (key == u"Parent") data->data()[key].set_Mode(SmartPtrMode::Weak);
}
}
Burada, System::Collections::Generic::Dictionary
içindeki data()
metodu, bu konteynerin temelindeki std::unordered_map
'e bir referans döndürür.