De vereiste komt voort uit de typevernietigingsregels van C++ en de noodzaak van het selecteren van deletor op compileertijd. Wanneer een arraytype aan een sjabloon wordt doorgegeven, verwordt het tot een pointer, waarbij de arrayomvangsinformatie wordt verwijderd die het onderscheiden van scalair (delete) van array (delete[]) deallocatie. std::unique_ptr lost dit op door middel van gedeeltelijke sjabloonspecialisatie: het primaire sjabloon std::unique_ptr<T> gebruikt std::default_delete<T> die scalair delete aanroept, terwijl std::unique_ptr<T[]> std::default_delete<T[]> instantiëert die delete[] aanroept. Deze expliciete syntaxis zorgt ervoor dat de compiler de juiste vernietigingscode genereert zonder runtime-type-inzicht of overhead.
Context: Een low-latency audioverwerkingsengine ontvangt PCM-monsterbuffers van een hardware-driver-API die float* retourneert, toegewezen via new float[buffer_size]. Deze buffers moeten door een keten van digitale signaalverwerkingsfilters gaan, terwijl strikte real-time eisen en uitzonderingveiligheid behouden blijven.
Probleem: Het team had een slimme pointeroplossing nodig die RAII-veiligheid bood voor deze C-stijl-arrays zonder de overhead van std::vector's grootte/capaciteit tracking, wat de cache-lijnafstemmingseisen voor SIMD-bewerkingen zou schenden. Kritiek, het gebruik van scalair delete op array-toegewezen geheugen zou de heap corrumperen en de audiobuis zou crashen.
Ruwe pointer met handmatige verwijdering. Deze benadering gebruikte blote float* pointers met expliciete delete[] aanroepen in elk exit pad. Voordelen: Geen abstractie overhead en directe compatibiliteit met de hardware-API. Nadelen: Uitzondering-onveilig; als een filter tijdens het verwerken gooide, lekte de buffer, en het onderhouden van de correcte verwijderingslogica over twintig verschillende filterstadia werd onhoudbaar. Verworpen vanwege betrouwbaarheidsrisico's in productie.
std::vector<float> container. Het inpakken van buffers in std::vector zorgde voor automatische geheugensbeheer en grootte tracking. Voordelen: Uitzonderingveiligheid en beschikbaarheid van grenscontrole. Nadelen: std::vector slaat impliciet capaciteitswijzers op (typisch 24 bytes overhead), wat de vaste DMA-afstemmingsovereenkomsten met de audiokhardware verbrak. Bovendien veronderstelt std::vector wijzigbare eigendom en potentiële herallocatie, wat in conflict kwam met de vaste bufferpool van de driver.
std::unique_ptr<float[]> specialisatie. Deze oplossing maakte gebruik van std::unique_ptr<float[]> die automatisch std::default_delete<float[]> instantiëert. Voordelen: Geen overhead (grootte van één pointer), gegarandeerde delete[] aanroep, verplaatsbare semantiek voor efficiënte doorvoer van de filterketen en compileertijdpreventie van kopieën. Nadelen: Verliest runtime-grootte-informatie die parallelle tracking vereist, en std::make_unique<float[]>(size) waarde-initieert elementen wat misschien overbodig is voor POD-typen.
Besluit en resultaat. We kozen voor std::unique_ptr<float[]> in combinatie met een lichte span-achtige weergave voor grootte tracking. Dit biedt uitzonderingveiligheid zonder de afstemvereisten van de hardware te schenden. Het systeem verwerkte audiostromen gedurende maanden zonder geheugenlekken, en de expliciete array-specialisatie ving een kritieke fout tijdens het compileren op waar een ontwikkelaar probeerde std::unique_ptr<float> met array-new, waardoor de juiste syntaxis voor runtime gedwongen werd.
**Waarom verwerpt std::unique_ptr<Base[]> initialisatie van new Derived[N] wanneer std::unique_ptr<Derived> converteert naar std::unique_ptr<Base>?
Arraytypes vertonen niet-covariante gedragingen in tegenstelling tot enkele pointers. Terwijl Derived* impliciet converteert naar Base* via pointeraanpassing, kan Derived[] niet converteren naar Base[] omdat de indexeringsaritmetiek voor arrays afhankelijk is van de statische maat. Het toegankelijk maken van element i in een Base[] weergave van Derived[] zou onjuiste byte-offsets berekenen. Daarom verwijdert de array-specialisatie van std::unique_ptr expliciet de converterende constructeurs tussen verschillende arraytypes om te voorkomen dat misaligned geheugen wordt benaderd, terwijl de scalair versie de conversie toestaat (waarbij virtuele destructors voor veiligheid vereist zijn).
Hoe initialiseert std::make_unique<T[]>(n) elementen in vergelijking met std::make_unique<T>(args...), en waarom beperkt dit de toepasbaarheid?
De array-overload std::make_unique<T[]>(n) voert waarde-initialisatie uit op alle n elementen, wat scalairs zero-initialiseert of objecten standaard construeert. Dit verschilt van de scalair vormen die argumenten doorgeven aan de constructor van T. Dit onderscheid voorkomt het gebruik van std::make_unique voor arrays van niet-standaard-constructeerbare types, aangezien je geen constructorargumenten voor individuele elementen kunt doorgeven. Kandidaten proberen vaak std::make_unique<NonDefaultConstructible[]>(5, args), wat niet compileert, waardoor ofwel handmatige lussen of std::vector-gebruik met emplacement vereist is.
Welk ongedefinieerd gedrag manifesteert zich wanneer std::unique_ptr<T> (scalair) geheugen beheert van new T[N], en waarom blijven compilers stil?
De scalair std::unique_ptr maakt gebruik van std::default_delete<T>, die delete (scalair delete) aanroept. Wanneer dit wordt toegepast op array-toegewezen geheugen van new T[N], resulteert dit in een mismatch die ongedefinieerd gedrag met zich meebrengt; typisch wordt alleen het geheugen van het eerste element vrijgegeven of corrumpeert de metadata van de heap-allocator. Compilers waarschuwen niet omdat de sjabloonparameter T verwordt; new T[N] retourneert T*, en het type systeem verliest de arrayonderscheiding op het moment van constructie van std::unique_ptr. Deze stille mislukking is precies de reden waarom std::unique_ptr<T[]> bestaat als een afzonderlijk typeveilige alternatief.