Bugünkü yazımda C++'ın en büyük tuzaklarından birini açıklayacağım: reinterpret_cast. Bu yazının bir başka başlığı da şu olabilir: “İşte bu Olumsuz aradığınız oyuncu kadrosu!”
Duyurudan sonra devamını okuyun
Andreas Fertig, dünya çapında yüz yüze ve uzaktan kurslar sunan uzman bir C++ eğitmeni ve danışmanıdır. C++ standartları komitesinde yer almakta ve uluslararası konferanslarda düzenli olarak konuşmalar yapmaktadır. C++ Insights (https://cppinsights.io) ile C++ programcılarının C++'ı daha iyi anlamalarına yardımcı olan, uluslararası alanda tanınan bir araç geliştirdi.
Bu blog yazısının motivasyonu çeşitli eğitim kurslarından ve verdiğim bazı derslerden geliyor. C++23'ten beri standart kütüphanede yeni bir fonksiyon var: std::start_lifetime_as. Gömülü ortamlara odaklanan dersler verdiğimde veya konferanslar verdiğimde bunu yapmaya başladım. std::start_lifetime_as materyale dahil edilecektir. İlginç bir sonuçla.
Aldığım geri bildirimler aşağı yukarı şu şekilde:
- Çünkü buna ihtiyacım var
std::start_lifetime_aszaten yaptımreinterpret_cast? - Çünkü yapabilirim
reinterpret_castkullanmıyor musun?
Hiç gitmemiş olanlardan start_lifetime_as Daha fazla bilgiyi “C++'da kelime oyunları yazmanın doğru yolu – İkinci perde” adlı İngilizce makalemde bulabilirsiniz. Aşağıdaki örneği kullanıyorum:
struct ConfigValues {
uint32_t chksum;
std::array<uint32_t, 128> values;
};
bool ProcessData(std::span<unsigned char> bytes)
{
if(bytes.size() < sizeof(ConfigValues)) { return false; }
// #A
ConfigValues* cfgValues = reinterpret_cast<ConfigValues*>(bytes.data());
return HandleConfigValues(cfgValues);
}
Buradaki fikir, bir dizi ham baytı, burada adı verilen bilinen bir yapıya dönüştürmektir. ConfigValues. Birlikte start_lifetime_as Kendimi sık sık insanların bana bu ismi söylediği sohbetlerin içinde buluyorum reinterpret bu kodun beklendiği gibi çalışması gerektiğini ima eder. Beklenti, bu tür bir kodun tanımsız davranışlardan arınmış olması ve aslında bir işaretçiyi içermesidir. ConfigValuesnesne geri döner.
Duyurudan sonra devamını okuyun
Standardın diline ve C++ nesne modeline dayalı bu beklentiye katılmasam da, bu beklenti tanımsız davranışa yol açıyor. Tip uyumlu bir dilde, bir nesne ilgisiz başka bir nesneye dönüştürülemez.
En önemli ifade şu [expr.reinterpret.cast § 7]ki şunu söylüyor:
Bir nesneye yönelik bir işaretçi açıkça farklı türden bir nesneye yönelik bir işaretçiye dönüştürülebilir. Nesne işaretçisi türünde bir prvalue v, “işaretçi to cv T” türü nesne işaretçisine dönüştürüldüğünde, sonuç static_cast olur
Her şeyden önce, bu paragrafın tamamı nesnelerin kendisiyle değil, nesnelere yönelik işaretçilerle ilgilidir. sende bir şey var diyor ConfigValues birinde void* veya nesne türü olan başka herhangi bir veri türü. Nesne türü, işlev türü, başvuru türü ve referans türü dışında herhangi bir şeydir. void.
Notun ilerleyen kısımlarında kural, oraya gidip geri dönmenin mümkün olduğunu açıkça doğrulamaktadır. Örneğin:
ConfigValues cfg{};
ConfigValues* val{&cfg};
void* typeErased = reinterpret_cast<void*>(val);
ConfigValues* roundTripBackToVal = reinterpret_cast<ConfigValues*>(typeErased);
Bu, tür silme olarak adlandırılan yapıları kullanan yapılara izin verir, örneğin std::any. Farklı bir işaretçi türünün takma adını alabilirsiniz.
Yalnızca işaretçi için dönüştürme
Bu paragraf nesnenin kendisini dönüştürmekten bahsetmiyor. Yalnızca işaretçiyi dönüştürebilirsiniz.
C++ nesne modeli açısından, bir uygulamanın geçerli bir nesne oluşturması (ve ardından yok etmesi) gerekir. Ama şimdiye kadar yaratılmış olan her şey birdir ConfigValues-Nesne. reinterpret_cast başka türden bir işaretçiyi saklamanıza izin veren bir araçtır. Kullanmak istediğinizde işaretçiyi orijinal türüne geri dönüştürmeniz gerekir.
Sanmak reinterpret_cast bazı insanların beklediği gibi işe yarayacaktır:
struct Apple {
int x;
};
struct Orange {
int y;
};
Apple* grannySmith{new Apple{4}};
Orange* bali{reinterpret_cast<Orange*>(grannySmith)}; // #A
int y = bali->y;
int x = grannySmith->x;
C++ kurallarına göre bir nesnenin yaratılması ve yok edilmesi gerekir. #A bir nesne yarattıysa bunu yapmalı grannySmith-Nesneyi yok edin. Bu şaşırtıcı olurdu. Yani böyle bir silme işlemi yapma olanağınız olmaz std::any silinmiş bir türü sakladığı için uygulamak void* C++'ın soyut mekanizması açısından orijinal nesneyi yok eder. Bu, derleyicinin programı çökertecek diğer çeşitli optimizasyonları yapmasına olanak tanır.
İle reinterpret_cast tek şansın var, tek eşyan A başka bir adam gibi B takma ada, ancak bu işaretçiyi hiçbir zaman kullanma hakkına sahip olmadan B-Nesnelere erişim. Öte yandan oluşturulan start_lifetime_as örtülü olarak B-İşaretçi hedefindeki nesne Aaynı zamanda ömrünü uzatırken A bitti.
Bir nesneye hayat vermek
Bu durumlarda genellikle farklı türden bir nesneye hayat vermek istersiniz. Ve bu tam olarak bunun için std::start_lifetime_as Düşünce.
eğer sen std::start_lifetime_as bir işaretçiye gittiğinizde soyut makine o türden yeni bir nesne yarattığınızı anlar. gelen bir aramanın aksine new veya bir yığın nesnesi varsa, hiçbir kurucu yürütülmez. Her şey yalnızca C++ nesne modelinde gerçekleşir.
Başka bir işlevi daha var std::start_lifetime_as: Yeni bir nesnenin yaşamı başladığında, yine gerçek bir yıkıcıyı çağırmadan, otomatik olarak kökenin yaşamını sona erdirir. Burası çok önemli.
eğer ben std::start_lifetime_as Bunu önceki örneğime uyguladığımda doğru uygulama şöyle görünür:
struct Apple {
int x;
};
struct Orange {
int y;
};
Apple* grannySmith{new Apple{4}};
Orange* bali{std::start_lifetime_as<Orange>(grannySmith)}; // #A
int y = bali->y;
grannySmith = std::start_lifetime_as<Apple>(bali); // #B
int x = grannySmith->x;
#B'deki kod işaretçi ömrünü şu şekilde yeniden başlatır: Apple-Nesne.
Temel bulgular
İle reinterpret_cast sadece bir işaretçi dönüşümü elde edersiniz. Bu yeni işaretçiyi bir tür nesneye erişmek için kullanamazsınız.
Bir nesnenin ömrünü yeni bir tür olarak verilere erişmek için başlatmak istiyorsanız, std::start_lifetime_as.
(Ben)

Bir yanıt yazın