Uyumsuzluk, std::uses_allocator tür özelliğinden kaynaklanmaktadır; bu özellik std::string ve std::pmr::polymorphic_allocator kombinasyonu için false değerini değerlendirir. std::string, allocator_type'ını std::allocator<char> olarak katıştırırken, std::pmr::vector std::pmr::polymorphic_allocator<char> sağlar; bunlar, örtük dönüşüm veya miras ilişkisi olmayan ayrı, ilgisiz sınıf türleridir. Konteyner elemanları oluşturduğunda, tahsisçinin bir kurucu argümanı olarak geçirilip geçirilmeyeceğini belirlemek için std::uses_allocator_v<T, Alloc>'i sorgular; bu kontrol başarısız olduğu için, vektör std::string'i tahsisçi-farkında olarak kabul eder ve varsayılan kurucusunu çağırır; bu da, vektörün bellek kaynağından bağımsız olarak global new ve delete kullanır.
static_assert(!std::uses_allocator_v<std::string, std::pmr::polymorphic_allocator<char>>); // std::pmr::vector, tahsisçisini std::string'e geçmeyecek
Finansal risk hesaplama motorunun optimizasyonu sırasında, yığından desteklenen std::pmr::monotonic_buffer_resource kullanmak için bir sıcak yolu yeniden düzenledik ve yığın çakışmasını ortadan kaldırdık. Tüm geçici sembol adlarının monotonik bellekten sağlanmasını bekleyerek std::pmr::vectorstd::string temp_symbols olarak tanımladık, ancak performans profilinde std::string kurucuları içerisinde beklenmedik malloc çağrıları tespit edildi ve bellek kaynağının tamamen atlandığı görüldü.
Her bir std::string'i, kurucusuna açık bir std::pmr::polymorphic_allocator geçirerek elle oluşturmaya çalıştık, ancak bu, tahsis detaylarını daha yüksek seviyedeki iş mantığına ifşa etmeyi gerektiriyordu ve emplace_back gibi kullanışlı modifikatörlerin kullanılmasını engelliyordu. Diğer bir yaklaşım, std::string'den türeyen ve polimorfik bir tahsisçi kabul eden özel bir string sarmalayıcı oluşturmayı içeriyordu; ancak bu, Liskov ikame ilkesini ihlal ediyor ve konteyner yeniden tahsis edilirken nesne dilimleme risklerini artırıyordu. Nihayetinde, std::string'i std::pmr::string ile değiştirdik (std::basic_string<char, std::char_traits<char>, std::pmr::polymorphic_allocator<char> için bir takma ad), bu da allocator_type'ı polimorfik varyant olarak tanımlar. Bu, vektörün tahsisçisini otomatik olarak uses_allocator protokolü aracılığıyla iletmesine olanak tanıdı, sıcak yol üzerindeki tüm yığın tahsislerini ortadan kaldırdı ve gecikmeyi mikrosaniyelerden yüzlerce nanosaniyeye düşürdü.
İçsel dinamik tahsis gerçekleştiren bir özel sınıf nasıl std::pmr::polymorphic_allocator ile uyumlu hale getirilebilir; yalnızca inşaatçısında bir tahsisçi parametresi kabul etmek yetersizdir?
Bir sınıf, ya kamuya açık bir allocator_type tür takma adı sunarak ya da ilk parametresi std::allocator_arg_t ve ikinci parametresi tahsisçi türü olan bir kurucu sağlayarak tahsisçi-farkındalığını açıkça bildirmelidir. Bununla birlikte, std::uses_allocator<ClassName, Alloc>'i std::true_type üzerinden türetmek de gerekir. Bu açık reklâm olmadan, std::pmr::vector sınıfın tahsisçi-farkında olduğunu varsayar ve varsayılan başlangıç ile oluşturur; bu, herhangi bir iç tahsisin polimorfik bellek kaynağını atlamasına neden olur.
Neden std::allocator_traits<std::pmr::polymorphic_allocator<T>>::rebind_alloc<U>, std::pmr::vector ile std::string arasındaki uyumsuzluğu çözmez?
Yeniden bağlama, std::pmr::polymorphic_allocator<U> üretir; bu, std::allocator<U> ile uyumsuzdur çünkü bunlar örtük dönüşüm ilişkisi olmayan ayrı somut türlerdir. std::uses_allocator mekanizması, elemanın allocator_type'ının, konteynerin tahsisçi türüyle aynı olması veya ondan dönüştürülebilmesi gerektiğini gerektirir; sadece farklı bir değer türüne yeniden bağlanabilir olması yeterli değildir; çünkü std::string, std::allocator'ı katıştırır, konteynerin tahsisçisinin yeniden bağlanması, elemanın beklenen tahsisçi türünü değiştirmez.
std::pmr::monotonic_buffer_resource ile std::pmr::string kullanırken hangi belirli yaşam süresi riski doğar ve neden bu tespiti standart tahsisçilerden daha zor hale getirir?**
Çünkü std::pmr::polymorphic_allocator tür silinmiş durumdadır ve bir temel std::pmr::memory_resource'a işaret eden bir gösterici saklar, derleyici yaşam süresi kısıtlamalarını derleme zamanında uygulayamaz. Yığından desteklenen bir monotonic_buffer_resource'u referans alan bir std::pmr::string taşındığında veya kopyalandığında, bellek kaynağına olan gösterici asılı hale gelir; std::allocator genellikle global yığın (her zaman geçerli) kullandığı için, tampon imhadan sonra string'e erişmek, serbest bırakıldığını kullanmaya neden olur. Statik analizörler bunu tespit etmekte zorluk çekerler çünkü sanal do_allocate/do_deallocate arayüzü, tür sisteminden altta yatan kaynağın yaşam süresini gizler.