Vraag Voorkeursmethode voor `nieuwe 'pointer naar array


Waarom is dit een fout:

typedef int H[4];

H * h = new H;        // error: cannot convert 'int*' to 'int (*)[4]' in initialization

?

Verder, waarom is dit niet een fout:

H * h = new H[1];

?

Waarom vindt de compiler dat new H retourneert een int *, terwijl new H[1] retourneert een H * zoals verwacht ?

Met andere woorden: waarom is dat T * t = new T; is correct voor een algemeen type T, maar is niet correct wanneer T is een array-type?

Wat zou de canonieke manier zijn om een ​​eenvoudig array-type zoals dit via toe te wijzen new ?

Merk op dat dit een vereenvoudigd voorbeeld is, dus b.v. new int[4] is geen acceptabele oplossing - ik moet het daadwerkelijke type van de vorige gebruiken typedef.

Merk ook op dat ik me bewust ben dat gebruik std::vector, std::array, et al heeft over het algemeen de voorkeur boven array-arrays in de C-stijl, maar ik gebruik een "echte wereld" -gebruik waarbij ik moet werken met typen zoals het bovenstaande.


16
2018-05-13 07:30


oorsprong


antwoorden:


De C ++ -regel voor het retourneringstype en de waarde van new T is:

  • Als T is geen matrixtype, het retourtype is T *en de geretourneerde waarde is een verwijzing naar het dynamisch toegewezen object van het type T.
  • Als T is een array van type U, het retourtype is U *, en de geretourneerde waarde is een verwijzing naar het eerste element (waarvan het type is U) van de dynamisch toegewezen array van het type T.

Daarom, sinds uw H is een array van int, het retourtype van new H is int *, niet H *.

Volgens dezelfde regel new H[1] komt terug H *, maar merk op dat je technicaly een tweedimensionale array van hebt toegewezen ints, formaat 1 x 4.

De beste manier om dit te krijgen in generieke code is inderdaad te gebruiken auto:

auto h = new H;

Of, als u liever het aanwijzingsfeit benadrukt:

auto *h = new H;

Wat betreft de reden van deze ogenschijnlijke inconsistentie in de regels: verwijzingen naar arrays zijn vrij "gevaarlijk" in C ++ omdat ze zich nogal onverwacht gedragen (dat wil zeggen, je moet heel voorzichtig zijn om niet ongewenste effecten te produceren). Laten we naar deze code kijken:

typedef int H[4];
H *h = obtain_pointer_to_H_somehow();
h[2] = h[1] + 6;

Bij de eerste (en misschien zelfs tweede) blik lijkt de bovenstaande code 6 toe te voegen aan de tweede int in de array en sla het op in de derde int. Maar dat is niet wat het doet.

Net zoals voor int *p, p[1] is een int (op het adres sizeof(int) bytes offset van p), dus voor H *h, h[1] is een H, op het adres 4 * sizeof(int) bytes offset van h. Dus de code wordt geïnterpreteerd als: neem het adres op h, voeg toe 4 * sizeof(int) bytes aan, voeg dan 6 toe en sla het resulterende adres op bij offset 8 * sizeof(int) van h. Dat zal natuurlijk niet lukken h[2] vervalt tot een rwaarde.

OK dan, je repareert het als volgt:

*h[2] = *h[1] + 6;

Erger nog nu. [] bindt strakker dan *, dus dit zal in de 5e reiken int object na h (let op, er zijn er maar 4!), voeg 6 toe en schrijf dat in de 9e int na h. FTW in een willekeurig geheugen schrijven.

Om echt te doen waar de code waarschijnlijk voor bedoeld was, zou het als volgt gespeld moeten worden:

(*h)[2] = (*h)[1] + 6;

In het licht van het bovenstaande, en aangezien wat je gewoonlijk doet met een dynamisch toegewezen array toegang tot de elementen ervan is, is dit logischer new T[]terugbrengen T *.


15
2018-05-13 08:01



Belangrijkste vraag

[Wat is de] Voorkeursmethode voor new aanwijzer naar array

Beperking:

Merk ook op dat ik me bewust ben dat het gebruik van std :: vector, std :: array, et al in het algemeen de voorkeur heeft boven array-arrays in de C-stijl, maar ik heb een "echte wereld" use case waarbij ik moet werken met typen zoals de bovenstaand.

Antwoord:

voor de niet-array-case:

#include <memory>

auto h = std::make_unique<H>();

// h is now a std::unique_ptr<H> which behaves to all intents and purposes
// like an H* but will safely release resources when it goes out of
// scope
// it is pointing to a default-constructed H

// access the underlying object like this:
h.get(); // yields H*
*h;      // yields H&

Voor de array-case:

#include <memory>

auto h = std::make_unique<H[]>(4);

// h is now a std::unique_ptr<H[]> which behaves to all intents and purposes
// like an H* but will safely destruct and deallocate the array 
// when it goes out of scope
// it is pointing to an array of 4 default-constructed Hs

// access the underlying object like this:
h[1];     // yields H& - a reference to the 2nd H
h.get();  //yields H* - as if &h[0]

4
2018-05-13 09:06



In de C ++ -standaard new en new[] zijn specifiek gescheiden omdat ze verschillende toewijzers kunnen zijn om redenen van prestatie en efficiëntie; het toewijzen van een array versus een enkel object heeft verschillende gebruikspatronen waarvoor allocatorimplementaties worden geoptimaliseerd.

De syntaxis is waarschijnlijk anders, omdat de introspectietechnieken van het compilatietype die beschikbaar waren toen deze werd gestandaardiseerd, lang niet zo betrouwbaar waren als nu.

Om verstandige code te maken, zou IMO de volgende voorkeur hebben:

struct H { int values[4]; }

H * h = new H;

Op die manier, jouw H type logisch "bevat" uw array van vier int waarden - maar de structuur in het geheugen moet nog steeds compatibel zijn (assert(sizeof(H) == 4 * sizeof(int))); en je kunt object-style toewijzing van je arrays van vaste grootte gebruiken. Meer ... C ++ - ey.


2
2018-05-13 08:11



U kunt zien waarom als u de typedef-substitutie in principe handmatig uitvoert:

H * h = new H;

Reduceert tot:

int[4]* h = new int[4];

terwijl

H * h = new H[1];

vermindert tot:

(int*)[4] h = new int[1][4];

Vanwege de array om vervalregels aan te wijzen, is dit legaal.

Als je een echte oplossing wilt, en je weet het type H, kun je iets doen als:

typedef int* J;
typedef int H[4];

J j = new H; // This will work

1
2018-05-13 08:08