C++
automatyczny
Szukaj…
Uwagi
Słowo kluczowe auto
to nazwa typu reprezentująca typ automatycznie wywnioskowany.
To było już zastrzeżone słowo kluczowe w C ++ 98, odziedziczone po C. W starych wersjach C ++ można go było użyć do jawnego stwierdzenia, że zmienna ma automatyczny czas przechowywania:
int main()
{
auto int i = 5; // removing auto has no effect
}
To stare znaczenie zostało teraz usunięte.
Podstawowa próbka automatyczna
auto
kluczowe auto
zapewnia automatyczne odjęcie typu zmiennej.
Jest to szczególnie wygodne w przypadku długich nazw typów:
std::map< std::string, std::shared_ptr< Widget > > table;
// C++98
std::map< std::string, std::shared_ptr< Widget > >::iterator i = table.find( "42" );
// C++11/14/17
auto j = table.find( "42" );
z pętlami zależnymi od zasięgu :
vector<int> v = {0, 1, 2, 3, 4, 5};
for(auto n: v)
std::cout << n << ' ';
z lambdami :
auto f = [](){ std::cout << "lambda\n"; };
f();
aby uniknąć powtórzenia tego typu:
auto w = std::make_shared< Widget >();
aby uniknąć zaskakujących i niepotrzebnych kopii:
auto myMap = std::map<int,float>();
myMap.emplace(1,3.14);
std::pair<int,float> const& firstPair2 = *myMap.begin(); // copy!
auto const& firstPair = *myMap.begin(); // no copy!
Powodem kopiowania jest to, że zwracanym typem jest w rzeczywistości std::pair<const int,float>
!
szablony automatyczne i wyrażenia
auto
może również powodować problemy w przypadku użycia szablonów wyrażeń:
auto mult(int c) {
return c * std::valarray<int>{1};
}
auto v = mult(3);
std::cout << v[0]; // some value that could be, but almost certainly is not, 3.
Powodem jest to, że operator*
na valarray
daje ci obiekt proxy, który odwołuje się do valarray
jako sposobu leniwej oceny. Korzystając z auto
, tworzysz wiszące odniesienie. Zamiast mult
zwrócił std::valarray<int>
, wtedy kod na pewno wypisze 3.
auto, const i referencje
Samo słowo kluczowe auto
reprezentuje typ wartości podobny do int
lub char
. Można go modyfikować za pomocą słowa kluczowego const
i symbolu &
aby reprezentować odpowiednio typ const lub typ odwołania. Te modyfikatory można łączyć.
W tym przykładzie s
jest typem wartości (jego typ będzie wywnioskowany jako std::string
), więc każda iteracja pętli for
kopiuje ciąg z wektora do s
.
std::vector<std::string> strings = { "stuff", "things", "misc" };
for(auto s : strings) {
std::cout << s << std::endl;
}
Jeśli s.append(" and stuff")
pętli modyfikuje s
(na przykład przez wywołanie s.append(" and stuff")
), tylko ta kopia zostanie zmodyfikowana, a nie oryginalny element strings
.
Z drugiej strony, jeśli s
jest zadeklarowane za pomocą auto&
będzie to typ odwołania (domyślnie jest to std::string&
), więc przy każdej iteracji pętli zostanie przypisane odwołanie do ciągu w wektorze:
for(auto& s : strings) {
std::cout << s << std::endl;
}
W treści tej pętli modyfikacje s
będą miały bezpośredni wpływ na element strings
których się odwołuje.
Na koniec, jeśli s
zostanie zadeklarowane jako const auto&
, będzie to stały typ odwołania, co oznacza, że przy każdej iteracji pętli zostanie mu przypisane stałe odniesienie do łańcucha w wektorze:
for(const auto& s : strings) {
std::cout << s << std::endl;
}
W ramach tego ciała pętli, s
nie mogą być zmienione (np żadnych metod const mogą być wywoływane na nim).
Kiedy używasz auto
z pętlą opartą for
, ogólnie dobrą praktyką jest używanie const auto&
jeśli korpus pętli nie zmodyfikuje zapętlonej struktury, ponieważ pozwala to uniknąć niepotrzebnych kopii.
Końcowy typ zwrotu
auto
jest używane w składni dla końcowego typu powrotu:
auto main() -> int {}
co jest równoważne z
int main() {}
Najbardziej użyteczny w połączeniu z decltype
do użycia parametrów zamiast std::declval<T>
:
template <typename T1, typename T2>
auto Add(const T1& lhs, const T2& rhs) -> decltype(lhs + rhs) { return lhs + rhs; }
Ogólna lambda (C ++ 14)
C ++ 14 pozwala na użycie auto
w argumencie lambda
auto print = [](const auto& arg) { std::cout << arg << std::endl; };
print(42);
print("hello world");
Ta lambda jest w większości odpowiednikiem
struct lambda {
template <typename T>
auto operator ()(const T& arg) const {
std::cout << arg << std::endl;
}
};
i wtedy
lambda print;
print(42);
print("hello world");
obiekty auto i proxy
Czasami auto
może zachowywać się niezupełnie tak, jak oczekiwał tego programista. Typ dedukuje wyrażenie, nawet jeśli dedukcja typu nie jest właściwa.
Na przykład, gdy w kodzie są używane obiekty proxy:
std::vector<bool> flags{true, true, false};
auto flag = flags[0];
flags.push_back(true);
flag
nie byłaby bool
, ale std::vector<bool>::reference
, ponieważ dla specjalizacji bool
vector
szablonowy operator []
zwraca obiekt proxy z zdefiniowanym operatorem konwersji operator bool
.
Gdy flags.push_back(true)
modyfikuje kontener, to pseudo-odwołanie może w końcu zwisać, odnosząc się do elementu, który już nie istnieje.
Umożliwia także następną sytuację:
void foo(bool b);
std::vector<bool> getFlags();
auto flag = getFlags()[5];
foo(flag);
vector
jest natychmiast odrzucany, więc flag
jest pseudo-odniesieniem do elementu, który został odrzucony. Wezwanie do foo
wywołuje niezdefiniowane zachowanie.
W takich przypadkach możesz zadeklarować zmienną za pomocą auto
i zainicjować ją, rzutując na typ, który chcesz wywnioskować:
auto flag = static_cast<bool>(getFlags()[5]);
ale w tym momencie po prostu zastąpienie auto
bool
ma większy sens.
Innym przypadkiem, w którym obiekty proxy mogą powodować problemy, są szablony wyrażeń . W takim przypadku szablony czasami nie są zaprojektowane tak, aby przetrwały poza bieżącym pełnym wyrażeniem ze względu na wydajność, a użycie obiektu proxy w następnym powoduje niezdefiniowane zachowanie.