В С++11 ключевое auto, которое существовало и до С++11, но очень редко использовалось, получило санкции на новый для себя вид деятельности: выводить тип. В 2011г. был принят стандарт С++11, где ключевое слово auto стало возможно использовать для автоматического вывода типа переменной.
Простейший способ применения ключевого слово auto выглядит так:
1 2 3 4 5 6 7 |
int main{ auto i = 10; //из значения 10 выводится тип int, который паяется переменной i auto d = 3.14; //из значения 3.14 выводится тип double, который паяется переменной d auto ld = 3.14L; //из значения 3.14 выводится тип long double, который паяется переменной ld auto S = "c_string";//из значения c_string выводится тип const char*, который паяется переменной S } |
Можно легко выводить тип указателя на функцию, без грандиозной мозголомки:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> using namespace std; int foo(){ return 100; } int main(){ auto temp = foo; cout << temp(); } |
В общем, тип для переменной задаётся такой же, как тип инициализатора. Инициализатор — это результат правой части.
Подобно обычному объявлению, где тип указывается один раз для некоторого множества однотипных переменных, можно указывать auto, с тем условием, что типы должны быть совместимы друг с другом:
1 2 3 4 |
int main(){ auto i = 10, *p = &i; // для i выводится int; для *p выводится int auto x = 2, y = 2.; // ошибка, типы не совместимы: выводится int и double } |
Спецификатор auto гораздо более удобен в типах, которые или сложно определить самостоятельно, или долго писать:
1 2 |
std::vector<double> scores; std::vector<double>::iterator pv = scores.begin(); //утомительная часть |
Здесь, в коде над этой строкой, для переменной pv используется простой тип, но писать такое бывает утомительно, использование auto делает код более лаконичным и приятным для глаз.
Начиная с С++11 этот же код может выглядеть так:
1 2 |
std::vector<double> scores; auto pv = scores.begin (); //cтрочка стала понятнее/с |
К сожалению, выводимый тип спецификатором auto не всегда точно совпадает с типом инициализатора, иногда компилятор подправляет тип так, чтобы тип соответствовал обычным правилам инициализации. Вот, например, пример для ссылки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> using namespace std; int main(){ int x = 100; int y = 200; int &rx = x; //rx псевдоним x auto a = rx; //из rx выводится int, хотя сам rx - ссылка на int a = y; cout << x << '\n'; //Пошло различие cout << a << '\n'; } |
Если думать, что auto всегда выведет нужный тип, то можно выстрелить себе в ногу. Этот пример достаточно простой, поэтому понять его можно относительно быстро. Можно ожидать, что с помощью auto выведется ссылочный тип, но выводится совсем не ссылочный тип, а обычный тип.
Одна из запутанных тем — это указатели на константы и константные указатели. Абсолютно любой пионер той темы может заявить нечто наподобие эдакого: "Вроде и звучит просто, и рассказать могу, но как дело до кода доходит, вообще ничего не могу разобрать". Суть в том, что в указателе на константу можно менять сам указатель, но не значение, на которое он указывает, а в константном указателе можно менять значение, но не сам указатель. Так вот, к чему я это пишу, const разделяют на const верхнего уровня и на const нижнего уровня. Указатель — это самостоятельный объект, а то, на что он указывает, это его косвенная составляющая. Если квалификатор const применяется к самому указателю, то такой const называют const верхнего уровня, если квалификатор const применяется для косвенной составляющей, то такой const называют const нижнего уровня.
Возвращаемся к спецификатору типа, к auto . Спецификатор auto обычно откидывает const верхнего уровня.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <iostream> using namespace std; int main(){ int i = 500; const int ci = i, &cr = ci; auto b = ci; // b - int (const верхнего уровня в ci отброшен) b++; //b свободно меняется // ci++; //ci менять нельзя auto c = cr; //с - int (cr - псевдоним для ci c const верхнего уровня) // cr++; //нельзя изменить const int auto d = &i; //d - int* (присваивание адреса целочисленного, поэтому d - указатель на int) cout << *d << '\n'; auto e = &ci; //e - указатель на константный int (const нижнего уровня не отбрасывается) e++; //Сам указатель свободно меняется (*e)++; //Но его косвенная сущность неизменяема } |
Возможно, пример покажется сложным, но это не так. Понимание этого примера напрямую зависит от понимания природы указателя: есть объект — указатель, а есть объект — косвенная сущность указателя. Константу можно применить как самому указателю, так и к его косвенной сущности. Спецификатор auto отбрасывает такой const, который применяется к самому указателю. Вот и весь секрет примера.
Спецификатор auto допускает применение к себе ключевого слова const. Поэтому, чтобы заявить компилятору о своих претензиях на неизменяемость переменной, тип которой выводится с помощью auto, перед auto нужно поставить const квалификатор:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> using namespace std; int main(){ int i = 300; int *p = &i; const auto ptr = p; ptr++; //указатель, выведенный спецификатором auto приобрёл маркер неизминеяемости } |
Подобный приём используется, если выводимый тип должен быть ссылочным типом:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> using namespace std; int main(){ int i = 300; int &p = i; auto &a = p; a = 500; cout << i << '\n'; //В i 500 } |
В отличие от указателей ссылки не являются объектами, поэтому у них нет особого разделения на const верхнего или нижнего уровня. Все const для ссылок являются const нижнего уровня, они относятся к объекту, а не к ссылке, поэтому auto не игнорирует const у ссылок.
1 2 3 |
int main(){ auto x; //нет инициализатора, выводить не из чего } |
В некоторых случаях использование auto может помочь избежать прячущейся проблемы. Трудно привести пример проявления, но описать одну из проблем такого вида можно.
1 2 3 4 5 6 7 8 9 |
#include <vector> int main(){ vector<int> v; //вектор заполняется каким-либо образом unsigned sz = v.size(); //запоминание размера вектора в переменную sz типа unsigned int } |
Изучая документацию, можно видеть, что возвращается беззнаковое целое:
Что-нибудь эдакое: Member type size_type is an unsigned integral type. — в двух словах здесь говориться об unsigned int. Поэтому многие разработчики, опираясь на подобную информацию, считают, что код, написанный в показанном листинге вполне нормальный. Этот код нормальный до тех пор, пока программа работает в операционной системе, где разные типы совпадают свойствами вместимости. При переносе программы на другую систему возможно начало проблемы. В 32-х разрядных Windows тип v.size() и unsigned int эквивалентны, но на 64-х разрядных windows типы различаются: v.size() возвращает тип, который способен хранить 64 бита, а unsigned int способен хранить 32 бита. Двумя словами — рассогласования вместимости. Подробности я оставлю, они описаны в книге Мейерса. Суть дела в том, что использование auto помогает вывести правильный тип v.size(), благодаря чему подобного вида проблем можно избежать даже не зная о том, что на них возможно было наткнуться.
Вот ещё хорошие особенности auto:
1 |
auto result = [](const auto& p1, const auto &p2){ return *p1 < *p2; }; |
Что такое лямбда-выражения в этой статье не оговаривается — это отдельная многословная тема.
Иногда программисту нужен точный тип, а auto выводит неточное представление, а приблизительное. В таких случаях не остаётся ничего, кроме того, чтобы использовать явно указываемый тип. Простейший пример такого проявления — вектор булевых значений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <vector> #include <iostream> using namespace std; vector<bool> foo(){ vector<bool> v{ true,true,true,true,true,true }; return v; } int main() { auto y = foo()[5]; //vector<bool>::reference bool x = foo()[5]; //bool } |
По одному только типу, который получается в результате манипуляций, можно судить, что можно ждать подвоха, ведь логичнее будет, чтобы auto выводил bool, но он выводит странное vector
Это, конечно, не всё, что можно рассказать о спецификаторе auto, но этого достаточно, чтобы познакомиться с ним и оценить некоторые преимущества. Главное его преимущество в том, что он очень удобен и сокращает код, главный недостаток в том, что он выводит типы не всегда абсолютно точно, а это потенциальный источник проблем.
Использованные материалы:
#include <iostream>
using namespace std;
int main(){
int i = 300;
int *p = &i;
const auto ptr = i;
ptr++; //указатель, выведенный спецификатором auto приобрёл маркер неизминеяемости
}
Мне кажется, что у вас здесь опечатка. ptr не будет const указателем на int, а будет просто const int.
Надо было указать: const auto ptr = p;
Да. Спасибо!
Это не сработает, так как компилятор не может определить типы данных для параметров функции a и b во время компиляции.
Привет