Utvidet aggregert initialisering i C++17

 C Programming >> C C# Program >  >> C++
Utvidet aggregert initialisering i C++17

Ved å oppgradere en kompilator til C++17, sluttet en bestemt kodebit som så rimelig ut å kompilere.

Denne koden bruker ingen utdaterte funksjoner som std::auto_ptr eller std::bind1st som ble fjernet i C++ 17, men den sluttet å kompilere likevel.

Ved å forstå denne kompileringsfeilen vil vi bedre forstå en ny funksjon i C++17:utvidet aggregatinitialisering .

Koden i C++14

Tenk på følgende kode:

template<typename Derived>
struct Base
{
private:
    Base(){};
    friend Derived;
};

struct Derived : Base<Derived>
{
};

int main()
{
    Derived d{};
}

Denne koden er et klassisk triks relatert til CRTP, for å unngå å sende feil klasse til CRTP-basisklassen.

Faktisk, i C++14 kompileres koden ovenfor, men en litt modifisert versjon der den CRTP-avledede klassen ikke overfører seg selv som en malparameter til basisklassen, kompilerer ikke selv i C++14:

template<typename Derived>
struct Base
{
private:
    Base(){};
    friend Derived;
};

struct X{};

struct Derived : Base<X> // passing the wrong class here
{
};

int main()
{
    Derived d{};
}

Når du prøver å konstruere Derived , må den kalle konstruktøren til dens basisklasse Base men sistnevnte er privat og bare friend med malparameteren. Malparameteren må være Derived for at koden skal kompileres.

Her er kompileringsfeilen i C++14 for det andre tilfellet (kjør koden):

<source>: In function 'int main()':
<source>:17:15: error: use of deleted function 'Derived::Derived()'
   17 |     Derived d{};
      |               ^
<source>:11:8: note: 'Derived::Derived()' is implicitly deleted because the default definition would be ill-formed:
   11 | struct Derived : Base<X>
      |        ^~~~~~~
<source>:11:8: error: 'Base<Derived>::Base() [with Derived = X]' is private within this context
<source>:5:5: note: declared private here
    5 |     Base(){};
      |     ^~~~

Og i C++14 kompilerer den første versjonen fint. Alt bra.

Koden i C++17

La oss ta igjen vår første korrekte versjon som kompileres i C++14:

template<typename Derived>
struct Base
{
private:
    Base(){};
    friend Derived;
};

struct Derived : Base<Derived>
{
};

int main()
{
    Derived d{};
}

Hvis vi prøver å kompilere den med C++17, får vi følgende feilmelding:

<source>: In function 'int main()':
<source>:15:15: error: 'Base<Derived>::Base() [with Derived = Derived]' is private within this context
   15 |     Derived d{};
      |               ^
<source>:5:5: note: declared private here
    5 |     Base(){};
      |     ^~~~

Base er fortsatt friend med Derived , hvorfor vil kompilatoren ikke godta å konstruere en Derived objekt?

Kan du se problemet?

Ta deg tid til å se på koden...

Hvis du ikke ser hvorfor dette ikke kompileres, vil det være desto mer lærerikt hvis du har brukt tid på å tenke på det...

Har du funnet den ennå?

Ok, la oss se hva som skjer her.

Utvidet aggregert initialisering

En av funksjonene som C++17 bringer med seg, er at den utvider den samlede initialiseringen.

Aggregert initialisering er når et anropssted konstruerer et objekt ved å initialisere medlemmene uten å bruke en eksplisitt definert konstruktør. Her er et eksempel:

struct X
{
    int a;
    int b;
    int c;
};

Vi kan da konstruere X på følgende måte:

X x{1, 2, 3};

Anropssiden initialiserer a , b og c med 1 , 2 og 3 , uten noen konstruktør for X . Dette er tillatt siden C++11.

Men reglene for at dette skal fungere er ganske strenge:klassen kan ikke ha private medlemmer, basisklasser, virtuelle funksjoner og mange andre ting.

I C++17 ble en av disse reglene avslappet:vi kan utføre aggregert initialisering selv om klassen har en basisklasse. Anropsstedet må deretter initialisere basisklassen.

Tenk for eksempel på følgende kode:

struct X
{
    int a;
    int b;
    int c;
};

struct Y : X
{
    int d;
};

Y arver fra X . I C++14 diskvalifiserer dette Y fra samlet initialisering. Men i C++17 kan vi konstruere en Y slik:

Y y{1, 2, 3, 4};

eller

Y y{ {1, 2, 3}, 4};

Begge syntaksene initialiserer a , b , c og d til 1 , 2 , 3 og 4 henholdsvis.

Vi kan også skrive dette:

Y y{ {}, 4 };

Dette initialiserer a , b og c til 0 og d til 4 .

Merk at dette ikke tilsvarer dette:

Y y{4};

Ettersom dette initialiserer a (ikke d ) til 4 og b , c og d til 0 .

Vi kan også spesifisere en del av attributtene i X :

Y y{ {1}, 4};

Dette initialiserer a til 1 , b og c til 0 og d til 4 .

Nå som vi er kjent med utvidet aggregert initialisering, la oss gå tilbake til den opprinnelige koden vår.

Hvorfor koden vår sluttet å kompilere

Her var koden vår som kompilerte fint i C++14 og sluttet å kompilere i C++17:

template<typename Derived>
struct Base
{
private:
    Base(){};
    friend Derived;
};

struct Derived : Base<Derived>
{
};

int main()
{
    Derived d{};
}

Legg merke til klammeparentesene på anropsstedet for konstruksjonen av Derived ? I C++17 utløser de samlet initialisering, og de prøver å instansiere Base , som har en privat konstruktør. Dette er grunnen til at den slutter å kompilere.

Det som er interessant å merke seg er at det er anropsstedet til konstruktøren som konstruerer basisklassen, og ikke selve konstruktøren . Faktisk, hvis vi endrer Base-klassen slik at den blir friend med anropsstedet til konstruktøren, kompileres koden fint i C++17 også:

template<typename Derived>
struct Base
{
private:
    Base(){};
    friend int main(); // this makes the code compile
};

struct Derived : Base<Derived>
{
};

int main()
{
    Derived d{};
}

Selvfølgelig kommer vi ikke til å beholde koden på den måten, med en friend til hvert anropssted! Denne endringen var bare for å illustrere det faktum at anropsstedet direkte kaller konstruktøren av basisklassen.

For å fikse koden kan vi... fjerne klammeparentesene:

template<typename Derived>
struct Base
{
private:
    Base(){};
    friend Derived;
};

struct Derived : Base<Derived>
{
};

int main()
{
    Derived d;
}

Og den kompilerer ok igjen.

Vær imidlertid oppmerksom på at vi ikke lenger drar nytte av verdiinitialisering. Hvis Derived eller klasse skulle inneholde datamedlemmer, må vi sørge for å initialisere dem i eksplisitt deklarerte konstruktører eller når de erklærer disse medlemmene i klassen.

Dette eksemplet lar oss bedre forstå hvordan aggregert initialisering fungerer og hvordan den endret seg i C++17. Morsomt hvor mye det kan lære oss å fjerne to karakterer!

Du vil også like

  • Bør strukturer ha konstruktører i C++
  • 5 måter å bruke seler kan gjøre C++-koden din mer uttrykksfull
  • Den virkelige forskjellen mellom struktur og klasse
  • Hvordan konstruere C++-objekter uten å lage kopier