initialiseret automatisk

initialiseret automatisk

Sandsynligvis den mest brugte funktion i C++11 er auto. Takket være auto bestemmer compileren typen af ​​en variabel fra dens initialisering. Men hvad er meningen med sikkerhedskritisk software?

Fakta om auto

Automatisk typefradrag med auto er yderst praktisk. For det første sparer du en masse unødvendig indtastning, især med udfordrende skabelonudtryk; for det andet er compileren aldrig - i modsætning til programmøren - forkert.

Jeg sammenligner i den næste liste de eksplicitte og de udledte typer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <vector>

int myAdd(int a,int b){ return a+b; }

int main(){

 // define an int-value
 int i= 5; // explicit
 auto i1= 5; // auto
 
 // define a reference to an int
 int& b= i; // explicit
 auto& b1= i; // auto
 
 // define a pointer to a function
 int (*add)(int,int)= myAdd; // explicit
 auto add1= myAdd; // auto
 
 // iterate through a vector
 std::vector<int> vec;
 for (std::vector<int>::iterator it= vec.begin(); it != vec.end(); ++it){} 
 for (auto it1= vec.begin(); it1 != vec.end(); ++it1) {}

}

Compileren bruger reglerne for skabelonargumentfradrag for at få typen af ​​variablen. Derfor fjernes den ydre konst eller flygtige kvalifikation og referencer. Det næste eksempel viser denne adfærd for konstant og referencer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(){
 
 int i= 2011;
 const int i2= 2014;
 const int& i3= i2;
 
 auto a2= i2; // int
 auto a3= i3; // int
 
}

Men hvordan kan jeg være sikker på, at a2 eller a3 er af typen int, selvom jeg brugte en variabel af typen const int eller const int&til at initialisere dem? Nogle gange udleder jeg det forkert. Svaret er enkelt. Kompileren kender sandheden. Den eneste erklærede klasseskabelon GetType hjælper mig meget.

template <typename T>
class GetType; 

Hvis jeg bruger den eneste erklærede klasseskabelon, vil compileren straks klage. Definitionen mangler. Det er den egenskab, jeg har brug for. Compileren fortæller mig præcis, hvilken type klasseskabelon, der ikke kan instansieres. Først til den udvidede kildekode. Jeg deaktiverede følgende kildekode for at forsøge at instansiere den eneste erklærede klasseskabelon.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <get_type.hpp>

int main(){
 
 int i= 2011;
 const int i2= 2014;
 // GetType<decltype(i2)> myType;
 const int& i3= i2;
 // GetType<decltype(i3)> myType;
 
 auto a2= i2; 
 // GetType<decltype(a2)> myType;
 auto a3= i3;
 // GetType<decltype(a3)> myType;
 
}

GetType-kaldet i linje 7, 9, 12 og 14 bruger specifikationen decltype, som giver dig den nøjagtige type af den erklærede variabel. Resten er kun hårdt arbejde. Jeg kommenterede successivt hvert GetType-udtryk. Et dybt kig på fejlmeddelelserne fra g++-kompilatorerne er meget interessant.

Nøgleudtrykkene for fejlmeddelelsen har en rød linje. imponeret? Men endnu en gang. Hvad er meningen med sikkerhedskritisk software?

Initialiser mig!

auto bestemmer sin type ud fra en initializer. Det betyder ganske enkelt. Uden en initializer er der ingen type og derfor ingen variabel. For at sige det positivt. Compileren sørger for, at hver type initialiseres. Det er en god bivirkning af auto, der nævnes for sjældent.

Det gør ingen forskel, om du har glemt at initialisere en variabel eller ikke har lavet den på grund af en forkert forståelse af sproget. Resultatet er simpelthen det samme:udefineret adfærd. Med auto kan du overvinde disse grimme fejl. Være ærlig. Kender du alle regler for initialisering af en variabel? Hvis ja, tillykke. Hvis ikke, læs artiklens standardinitialisering og alle refererede artikler i denne artikel. Jeg har ingen anelse om, hvorfor de brugte følgende udsagn:"objekter med automatisk lagringsvarighed (og deres underobjekter) initialiseres til ubestemte værdier". Denne formulering forårsager mere skade end gavn. Lokale variabler vil ikke blive initialiseret som standard.

Jeg ændrede det andet program med standardinitialisering for at gøre den udefinerede adfærd mere indlysende.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// init.cpp

#include <iostream>
#include <string>
 
struct T1 {};
 
struct T2{
 int mem; // Not ok: indeterminate value
 public:
 T2() {} 
};
 
int n; // ok: initialized to 0
 
int main(){
 
 std::cout << std::endl;
 
 int n; // Not ok: indeterminate value
 std::string s; // ok: Invocation of the default constructor; initialized to "" 
 T1 t1; // ok: Invocation of the default constructor 
 T2 t2; // ok: Invocation of the default constructor
 
 std::cout << "::n " << ::n << std::endl;
 std::cout << "n: " << n << std::endl;
 std::cout << "s: " << s << std::endl;
 std::cout << "T2().mem: " << T2().mem << std::endl;
 
 std::cout << std::endl;
 
}

Først til scope resolutions-operatoren::i linje 25. ::adresserer det globale omfang. I vores tilfælde er variablen n i linje 14. Mærkeligt nok har den automatiske variabel n i linje 25 værdien 0. n har en udefineret værdi, og derfor har programmet udefineret adfærd. Det gælder ikke for det variable mem i klassen T2. mem returnerer en udefineret værdi.

Nu omskriver jeg programmet ved hjælp af auto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// initAuto.cpp

#include <iostream>
#include <string>
 
struct T1 {};
 
struct T2{
 int mem= 0; // auto mem= 0 is an error
 public:
 T2() {}
};
 
auto n= 0;
 
int main(){
 
 std::cout << std::endl;
 
 using namespace std::string_literals;
 
 auto n= 0;
 auto s=""s; 
 auto t1= T1(); 
 auto t2= T2();
 
 std::cout << "::n " << ::n << std::endl;
 std::cout << "n: " << n << std::endl;
 std::cout << "s: " << s << std::endl;
 std::cout << "T2().mem: " << T2().mem << std::endl;
 
 std::cout << std::endl;
 
}

To linjer i kildekoden er særligt interessante. Først linje 9. Den nuværende standard forbyder det at initialisere ikke-konstante medlemmer af en klasse med auto. Derfor er jeg nødt til at bruge den eksplicitte type. Dette er fra mit perspektiv, kontra intuitivt. Her er en diskussion af C++ standardiseringsudvalget om dette emne:artikel 3897.pdf. For det andet, linje 23. C++14 får C++ strenge bogstaver. Du bygger dem ved at bruge en C-streng literal ("") og tilføjer suffikset s (""s). For nemheds skyld importerede jeg i linje 20:ved hjælp af navneområde std::string_literals.

Resultatet af programmet er ikke så spændende. Kun for fuldstændighedens skyld. T2().mem har værdien 0.

Omfaktorisering

Lige i øjeblikket vil jeg afslutte indlægget, at en ny brug af auto kommer til mit sind. auto understøtter meget godt refaktoriseringen af ​​din kode. For det første er det meget nemt at omstrukturere din kode, hvis der ikke er nogen form for information. For det andet sørger compileren automatisk for de rigtige typer. Hvad betyder det? Jeg giver svaret i form af et kodestykke. Først koden uden auto.

int a= 5;
int b= 10;
int sum= a * b * 3;
int res= sum + 10; 

Når jeg erstatter variablen b af type in med en dobbelt 10,5, skal jeg justere alle afhængige typer. Det er besværligt og farligt. Jeg skal bruge de rigtige typer og tage mig af indsnævring og andre intelligente fænomener i C++.

int a2= 5;
double b2= 10.5;
double sum2= a2 * b2 * 3;
double res2= sum2 * 10.5;

Denne fare er ikke til stede i tilfælde af auto. Alt sker automatisk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// refactAuto.cpp

#include <typeinfo>
#include <iostream>

int main(){
 
 std::cout << std::endl;

 auto a= 5;
 auto b= 10;
 auto sum= a * b * 3;
 auto res= sum + 10; 
 std::cout << "typeid(res).name(): " << typeid(res).name() << std::endl;
 
 auto a2= 5;
 auto b2= 10.5;
 auto sum2= a2 * b2 * 3;
 auto res2= sum2 * 10; 
 std::cout << "typeid(res2).name(): " << typeid(res2).name() << std::endl;
 
 auto a3= 5;
 auto b3= 10;
 auto sum3= a3 * b3 * 3.1f;
 auto res3= sum3 * 10; 
 std::cout << "typeid(res3).name(): " << typeid(res3).name() << std::endl;
 
 std::cout << std::endl;
 
}

De små variationer af kodestykket bestemmer altid den rigtige type res, res2 eller res3. Det er compilerens opgave. Variablen b2 i linie 17 er af typen double og derfor også res2; variablen sum3 i linje 24 bliver på grund af multiplikationen med float-literalen 3.1f en float-type og derfor også finaleresultatet res3. For at få typen fra compileren bruger jeg typeid-operatoren, der er defineret i headeren typeinfo.

Her får du resultaterne sort på gult.

imponeret? Også mig.

Hvad er det næste?

Initialiseringen med krøllede seler {} har meget til fælles med auto. Den bruges på samme måde ofte, hjælper med at læse koden og gør din kode mere sikker. Hvordan? Du vil se det i næste indlæg.