Aliasy typów
W C++ za pomocą jest słowem kluczowym tworzącym alias dla istniejącego typu danych. Aby utworzyć taki alias typu, używamy słowa kluczowego using , po którym następuje nazwa aliasu typu, znak równości i istniejący typ danych. Na przykład:
using Distance = double; // define Distance as an alias for type doublePo zdefiniowaniu alias typu może być używany wszędzie tam, gdzie potrzebny jest typ. Na przykład możemy utworzyć zmienną z aliasem typu jako typem:
Distance milesToDestination{ 3.4 }; // defines a variable of type doubleGdy kompilator napotka alias typu, podstawi alias typu. Przykładowo:
#include <iostream>
int main()
{
using Distance = double; // define Distance as an alias for type double
Distance milesToDestination{ 3.4 }; // defines a variable of type double
std::cout << milesToDestination << '\n'; // prints a double value
return 0;
}Wypisuje:
3.4
W powyższym programie najpierw definiujemy Distance jako alias dla typu double.
Następnie definiujemy zmienną o nazwie milesToDestination typu alias Distance. Ponieważ kompilator wie, że Distance jest aliasem typu, użyje typu aliasu, którym jest double. Zatem zmienna milesToDestination jest właściwie kompilowana jako zmienna typu double i będzie zachowywać się jak double pod każdym względem.
Na koniec drukujemy wartość milesToDestination, która jest drukowana jako double wartości.
Dla zaawansowanych czytelników
Aliasy typu mogą być również szablonowane. Omówimy to w lekcji 13.14 -- Przewodniki dedukcji i dedukcji szablonu klasy (CTAD).
Nazywanie aliasów typów
Historycznie rzecz biorąc, nie było zbyt spójnego sposobu nazewnictwa aliasów typów. Istnieją trzy popularne konwencje nazewnictwa (i spotkasz je wszystkie):
- Aliasy typów zakończone sufiksem „_t” („_t” jest skrótem od „typ”). Ta konwencja jest często używana przez bibliotekę standardową dla nazw typów o zasięgu globalnym (np.
size_tinullptr_t).
Ta konwencja została odziedziczona z C i była najpopularniejsza podczas definiowania własnych aliasów typów (a czasem innych typów), ale wypadła z łask we współczesnym C++. Należy pamiętać, że POSIX rezerwuje sufiks „_t” dla nazw typów o zasięgu globalnym, więc użycie tej konwencji może powoduje konflikty nazewnictwa typów w systemach POSIX.
- Aliasy typów zakończone sufiksem „_type” Ta konwencja jest używana przez niektóre standardowe typy bibliotek (np.
std::string) do nazywania aliasów typów zagnieżdżonych (np.std::string::size_type).
Ale wiele takich aliasów typów zagnieżdżonych w ogóle nie używa sufiksu (np. std::string::iterator), więc takie użycie jest w najlepszym razie niespójny.
- Aliasy typów, które nie używają sufiksu.
We współczesnym C++ konwencją jest nadawanie nazw aliasom typów (lub innym typom), które definiujesz, zaczynając od dużej litery i nie używając sufiksu. Wielka litera pomaga odróżnić nazwy typów od nazw zmiennych i funkcji (które zaczynają się od małej litery) i zapobiega kolizjom nazw między nimi.
Kiedy. stosując tę konwencję nazewnictwa, często można spotkać się z następującym użyciem:
void printDistance(Distance distance); // Distance is some defined typeW tym przypadku Distance to typ i distance to nazwa parametru. W C++ rozróżniana jest wielkość liter, więc jest to w porządku.
Najlepsza praktyka
Nadawaj aliasy typów zaczynając od dużej litery i nie używaj sufiksu (chyba że masz konkretny powód, aby zrobić inaczej).
Nota autora
W niektórych przyszłych lekcjach z tej serii tutoriali nadal używane są Sufiks „_t” lub „_type” Prosimy o pozostawienie komentarza na temat tych lekcji, abyśmy mogli zapewnić ich zgodność z najlepszymi praktykami.
Aliasy typów nie są odrębnymi typami
Alias w rzeczywistości nie definiuje nowego, odrębnego typu (takiego, który jest uważany za odrębny od innych typów) - po prostu wprowadza nowy identyfikator dla istniejącego typu type.
Pozwala nam to robić rzeczy, które są poprawne pod względem składniowym, ale pozbawione znaczenia semantycznego. Na przykład:
int main()
{
using Miles = long; // define Miles as an alias for type long
using Speed = long; // define Speed as an alias for type long
Miles distance { 5 }; // distance is actually just a long
Speed mhz { 3200 }; // mhz is actually just a long
// The following is syntactically valid (but semantically meaningless)
distance = mhz;
return 0;
}Chociaż koncepcyjnie zamierzamy Miles i Speed mieć różne znaczenia, oba są po prostu aliasami dla typu long. Oznacza to, że Miles, Speed, I long można ich używać zamiennie. I rzeczywiście, gdy przypiszemy wartość type Speed do zmiennej typu Miles, kompilator widzi tylko, że przypisujemy wartość typu long do zmiennej typu long i nie będzie narzekał.
Ponieważ kompilator nie zapobiega tego rodzaju błędom semantycznym w przypadku aliasów typów, mówimy, że aliasy nie są bezpieczne dla typów. Mimo to są one nadal przydatne.
Ostrzeżenie
Należy uważać, aby nie mieszać wartości aliasów, które mają być semantycznie różne.
Na marginesie…
Niektóre języki obsługują koncepcję silnego typedef (lub aliasu silnego typu). Silny typedef w rzeczywistości tworzy nowy typ, który ma wszystkie oryginalne właściwości oryginalnego typu, ale kompilator zgłosi błąd, jeśli spróbujesz wymieszać wartości typu aliasowego i silnego typedef. Począwszy od C++20, C++ nie obsługuje bezpośrednio silnych typedefs (chociaż klasy wyliczeniowe omówione w lekcji 13.6 -- Wyliczenia o ograniczonym zakresie (klasy wyliczeniowe) są podobne), ale istnieje sporo zewnętrznych bibliotek C++, które implementują silne zachowanie podobne do typedef.
Zakres aliasu typu
Ponieważ zakres jest właściwością identyfikatora, identyfikatory aliasów typów podlegają tym samym regułom zakresu co identyfikatory zmiennych: typ Alias zdefiniowany wewnątrz bloku ma zasięg blokowy i można go używać tylko w obrębie tego bloku, natomiast alias typu zdefiniowany w globalnej przestrzeni nazw ma zasięg globalny i można go używać do końca pliku. W powyższym przykładzie Miles i Speed można ich używać tylko w main() .
Jeśli chcesz użyć jednego lub więcej aliasów typów w wielu plikach, można je zdefiniować w pliku nagłówkowym i #włączyć do dowolnych plików z kodem, które muszą używać definicji:
mytypes.h:
#ifndef MYTYPES_H
#define MYTYPES_H
using Miles = long;
using Speed = long;
#endifAliasy typów #included w ten sposób zostaną zaimportowane do globalnej przestrzeni nazw i tym samym będą miały globalny zakres.
Definicje typów
A typedef (co jest skrótem od „definicji typu”) to starszy sposób tworzenia aliasu dla typu. Aby utworzyć alias typedef, używamy typedef słowem kluczowym:
// The following aliases are identical
typedef long Miles;
using Miles = long;Opcje Typedef nadal znajdują się w C++ ze względu na kompatybilność wsteczną, ale we współczesnym C++ zostały w dużej mierze zastąpione aliasami typów.
Opcje Typedef mają kilka problemów składniowych. Po pierwsze, łatwo zapomnieć, czy pierwsza jest nazwa typedef, czy nazwa typu aliasu. Co jest poprawne?
typedef Distance double; // incorrect (typedef name first)
typedef double Distance; // correct (aliased type name first)Łatwo jest się cofnąć. Na szczęście w takich przypadkach kompilator będzie narzekał.
Po drugie, składnia typedef może stać się brzydka w przypadku bardziej złożonych typów. Na przykład tutaj jest trudny do odczytania typedef wraz z równoważnym (i nieco łatwiejszym do odczytania) aliasem typu:
typedef int (*FcnType)(double, char); // FcnType hard to find
using FcnType = int(*)(double, char); // FcnType easier to findW powyższej definicji typedef nazwa nowego typu (FcnType) jest ukryta w środku definicji, podczas gdy w aliasie typu nazwa nowego typu i reszta definicji są oddzielone znakiem równości.
Po trzecie, nazwa „typedef” sugeruje, że definiowany jest nowy typ, ale to nieprawda. typedef to po prostu alias.
Najlepsza praktyka
Preferuj aliasy typów zamiast typedefs.
Nomenklatura
Standard C++ używa terminu „nazwy typedef” zarówno dla nazw typedef, jak i aliasów typów.
W języku konwencjonalnym termin „typedef” jest często używany w znaczeniu „albo typedef, albo aliasu typu”, ponieważ w rzeczywistości robią to samo rzecz.
Kiedy powinniśmy używać aliasów typów?
Teraz, gdy omówiliśmy, czym są aliasy typów, porozmawiajmy o tym, do czego są przydatne.
Używanie aliasów typów do kodowania niezależnego od platformy
Jednym z głównych zastosowań aliasów typów jest ukrywanie szczegółów specyficznych dla platformy. Na niektórych platformach wartość int wynosi 2 bajty, a na innych 4 bajty. Zatem używanie int do przechowywania więcej niż 2 bajtów informacji może być potencjalnie niebezpieczne podczas pisania kodu niezależnego od platformy.
Ponieważ char, short, int, I long nie podaje rozmiaru ich, dość powszechne jest, że programy wieloplatformowe używają aliasów typów do definiowania aliasów obejmujących rozmiar typu w bitach. Na przykład int8_t będzie 8-bitową liczbą całkowitą ze znakiem, int16_t 16-bitową liczbą całkowitą ze znakiem i int32_t 32-bitowa liczba całkowita ze znakiem. Używanie aliasów typów w ten sposób pomaga zapobiegać błędom i wyjaśnia, jakie założenia zostały przyjęte co do rozmiaru zmiennej.
Aby mieć pewność, że każdy aliasowany typ zostanie przekształcony w typ o odpowiednim rozmiarze, aliasy typów tego rodzaju są zwykle używane w połączeniu z dyrektywami preprocesora:
#ifdef INT_2_BYTES
using int8_t = char;
using int16_t = int;
using int32_t = long;
#else
using int8_t = char;
using int16_t = short;
using int32_t = int;
#endifNa maszynach, gdzie liczby całkowite mają tylko 2 bajty, INT_2_BYTES można #definiować (jako ustawienie kompilatora/preprocesora), a program zostanie skompilowany z górnym zestawem aliasów typów. Na komputerach, gdzie liczby całkowite mają 4 bajty, pozostawienie INT_2_BYTES niezdefiniowanego spowoduje użycie dolnego zestawu aliasów typów. W ten sposób, o ile INT_2_BYTES jest #zdefiniowany poprawnie, int8_t będzie rozpoznawany na 1-bajtową liczbę całkowitą, int16_t będzie rozpoznawany na 2-bajtową liczbę całkowitą i int32_t będzie rozpoznawany na 4-bajtową liczbę całkowitą (używając kombinacji char, short, int, I long odpowiedniej dla maszyny, na której program jest kompilowany).
The typy całkowite o stałej szerokości (takie jak std::int16_t i std::uint32_t) i typ size_t (oba omówione w lekcji 4.6 -- Liczby całkowite o stałej szerokości i size_t) to w rzeczywistości po prostu aliasy typów do różnych podstawowych typów.
Dlatego też, gdy drukujesz 8-bitową liczbę całkowitą o stałej szerokości za pomocą std::cout, prawdopodobnie otrzymasz wartość znakową. Na przykład:
#include <cstdint> // for fixed-width integers
#include <iostream>
int main()
{
std::int8_t x{ 97 }; // int8_t is usually a typedef for signed char
std::cout << x << '\n';
return 0;
}Ten program wypisuje:
a
Ponieważ std::int8_t jest zazwyczaj typedef dla signed char, zmienna x będzie prawdopodobnie zdefiniowany jako signed char. Typy znakowe wypisują swoje wartości jako znaki ASCII, a nie jako wartości całkowite.
Używanie aliasów typów w celu ułatwienia odczytania typów złożonych
Chociaż do tej pory zajmowaliśmy się tylko prostymi typami danych, w zaawansowanym C++ typy mogą być skomplikowane i długotrwałe, jeśli chodzi o ręczne wprowadzanie na klawiaturze. Na przykład możesz zobaczyć funkcję i zmienną zdefiniowaną w ten sposób:
#include <string> // for std::string
#include <vector> // for std::vector
#include <utility> // for std::pair
bool hasDuplicates(std::vector<std::pair<std::string, int>> pairlist)
{
// some code here
return false;
}
int main()
{
std::vector<std::pair<std::string, int>> pairlist;
return 0;
}Wpisywanie std::vector<std::pair<std::string, int>> wszędzie tam, gdzie trzeba użyć tego typu, jest kłopotliwe i łatwo popełnić błąd podczas pisania. Dużo łatwiej jest użyć aliasu typu:
#include <string> // for std::string
#include <vector> // for std::vector
#include <utility> // for std::pair
using VectPairSI = std::vector<std::pair<std::string, int>>; // make VectPairSI an alias for this crazy type
bool hasDuplicates(VectPairSI pairlist) // use VectPairSI in a function parameter
{
// some code here
return false;
}
int main()
{
VectPairSI pairlist; // instantiate a VectPairSI variable
return 0;
}Znacznie lepiej! Teraz pozostaje nam tylko wpisać VectPairSI zamiast std::vector<std::pair<std::string, int>>.
Nie martw się, jeśli nie wiesz co std::vector, std::pair, albo te wszystkie szalone nawiasy kątowe już są. Jedyną rzeczą, którą naprawdę musisz zrozumieć, jest to, że aliasy typów pozwalają na przyjmowanie typów złożonych i nadawanie im prostszych nazw, co ułatwia czytanie kodu i oszczędza pisanie.
To prawdopodobnie najlepsze zastosowanie aliasów typów.
Używanie aliasów typów do dokumentowania znaczenia wartości
Aliasy typów mogą również pomóc w dokumentacji kodu i zrozumienie.
W przypadku zmiennych mamy identyfikator zmiennej, który pomaga udokumentować cel zmiennej. Rozważmy jednak przypadek wartości zwracanej przez funkcję. Typy danych takie jak char, int, long, double, I bool opisują typ wartość zwracaną przez funkcję, ale częściej chcemy wiedzieć, jakie znaczenie jest zwracana wartość.
Na przykład, biorąc pod uwagę następującą funkcję:
int gradeTest();Widzimy, że zwracana wartość jest liczbą całkowitą, ale co oznacza liczba całkowita? Ocena listowa? Liczba pominiętych pytań? Numer identyfikacyjny studenta? Kod błędu? Kto wie! Typ zwracany int nie wiele nam mówi. Jeśli będziemy mieli szczęście, dokumentacja funkcji istnieje gdzieś, do czego możemy się odwołać. Jeśli mamy pecha, musimy przeczytać kod i wywnioskować jego cel.
Zróbmy teraz równoważną wersję przy użyciu aliasu typu:
using TestScore = int;
TestScore gradeTest();Typ zwracany przez TestScore sprawia, że jest trochę bardziej oczywiste, że funkcja zwraca typ reprezentujący wynik testu.
Z naszego doświadczenia wynika, że tworzenie aliasu typu tylko w celu udokumentowania typu zwracanego przez pojedynczą funkcję nie jest tego warte (zamiast tego użyj komentarza). Jeśli jednak masz wiele funkcji przekazujących lub zwracających taki typ, warto utworzyć alias typu.
Używanie aliasów typów w celu łatwiejszej konserwacji kodu
Aliasy typów umożliwiają także zmianę podstawowego typu obiektu bez konieczności aktualizowania wielu zakodowanych na stałe typów. Na przykład, jeśli używałeś short do przechowywania numeru identyfikacyjnego ucznia, ale później zdecydowałeś, że zamiast tego potrzebujesz long , będziesz musiał przeszukać dużo kodu i zastąpić short z long. Prawdopodobnie trudno byłoby ustalić, które obiekty typu short były używane do przechowywania numerów identyfikacyjnych, a które do innych celów.
Jeśli jednak używasz aliasów typów, wówczas zmiana typów staje się tak prosta, jak aktualizacja aliasu typu (np. z using StudentId = short; Do using StudentId = long;).
Chociaż wydaje się to niezłą korzyścią, należy zachować ostrożność przy każdej zmianie typu, ponieważ zachowanie programu również może się zmienić. Jest to szczególnie prawdziwe, gdy zmiana typu aliasu typu na typ z innej rodziny typów (np. liczba całkowita na wartość zmiennoprzecinkową lub wartość ze znakiem na wartość bez znaku)! Nowy typ może powodować problemy z porównywaniem lub dzieleniem liczb całkowitych/zmiennoprzecinkowych lub inne problemy, których nie miał stary typ. Jeśli zmienisz istniejący typ na inny, Twój kod powinien zostać dokładnie przetestowany.
Wady i wnioski
Podczas pisania. aliasy oferują pewne korzyści, wprowadzają także do kodu kolejny identyfikator, który należy zrozumieć. Jeśli nie jest to równoważone jakąś korzyścią dla czytelności lub zrozumienia, to alias typu wyrządza więcej szkody niż pożytku.
Źle wykorzystany alias typu może przyjąć znajomy typ (taki jak std::string) i ukryć go pod niestandardową nazwą, którą w niektórych przypadkach trzeba sprawdzić (np. za pomocą inteligentnych wskaźników, co omówimy w przyszłości). rozdział), zaciemnianie informacji o typie może również zaszkodzić zrozumieniu, jak powinien działać typ.
Z tego powodu aliasy typów powinny być używane przede wszystkim w przypadkach, gdy istnieje wyraźna korzyść w zakresie czytelności kodu lub konserwacji kodu. Jest to w równym stopniu sztuka, jak i nauka. Aliasy typów są najbardziej przydatne, gdy można ich używać w wielu miejscach w kodzie, a nie w mniejszej liczbie.
Najlepsza praktyka
Używaj aliasów rozsądnie, gdy są. zapewniają wyraźną korzyść w zakresie czytelności kodu lub konserwacji kodu.
Czas quizu
Pytanie nr 1
Biorąc pod uwagę następujący prototyp funkcji:
int printData();Przekonwertuj int zwróć wartość do aliasu typu o nazwie PrintError Uwzględnij zarówno instrukcję aliasu typu, jak i zaktualizowany prototyp funkcji.

