Long long -- более длинное целое
Целочисленная переменная, размером, по крайней мере, 64 бита. Например: long long x = 9223372036854775807LL;
Нет, никаких long long long, и long нельзя рассматривать как short long long. См. также:
Nullptr -- литерал для задания нулевого указателя nullptr – это литер, который задает нулевой указатель; это не целочисленное значение: char* p = nullptr; int* q = nullptr; char* p2 = 0; // 0 все еще работает и p==p2 void f(int); void f(char*); f(0); // вызов f(int) f(nullptr); // вызов f(char*) void g(int); g(nullptr); // ошибка: nullptr не явялется типом int int i = nullptr; // ошибка: nullptr не является типом int
См. также:
Суффиксный синтаксис возвращаемого значения Давайте рассмотрим пример: template<class T, class U>??? mul(T x, U y) { return x*y; }
Что мы должны записать в качестве типа возвращаемого значения? Конечно же, это тип выражения x*y, но как нам его указать. Первая мысль, с помощью decltype: template<class T, class U> decltype(x*y) mul(T x, U y) // Проблема с видимостью! { return x*y; }
Этот вариант не работает, поскольку x и y используются за пределами их области видимости. Однако мы можем записать так: // Ужасно! И чревато ошибками template<class T, class U> decltype(*(T*)(0)**(U*)(0)) mul(T x, U y) { return x*y; }
И сказать, что этот вариант «не очень» - это ничего не сказать. Решение же заключается в помещении типа возвращаемого значения на его место – после аргументов: template<class T, class U> auto mul(T x, U y) -> decltype(x*y) { return x*y; }
Мы используем ключевое слово auto, которое говорит, что «тип возвращаемого значения будет выведен или указан позднее». На самом деле суффиксный синтаксис связан не столько с шаблонами и выводом типов, сколько с областью видимости.
Первый префикс List:: необходим только потому, что область типа List еще не начинается до второго List::. Вот более простой вариант: auto List::erase(Link* p) -> Link* { /*... */ }
Теперь ни одно из упоминаний Link не требует явной квалификации имен. См. также:
Template alias (известные ранее как “template typedef”) Как нам создать шаблон, «аналогичный другому шаблону», но, возможно, с несколькими указанными (привязанными, bound) шаблонными аргументами? Давайте рассмотрим пример: template<class T> // Стандартный вектор, использующий мой аллокатор using Vec = std::vector<T,My_alloc<T>>; // Элементы выделяются с помощьюMy_alloc Vec<int> fib = { 1, 2, 3, 5, 8, 13 }; // verbose и fib одного типа vector<int,My_alloc<int>> verbose = fib;
Ключевое слово using используется для получения линейной нотации: «имя, за которым следует то, на что оно ссылается». Мы попробовали использовать стандартное и довольно запутанное решение на основе typedef, но так и не смогли добиться полного и ясного решения, пока не пришли к менее запутанному синтаксису. Специализация работает (вы можете создать синоним (alias) для набора специализаций, но не можете специализировать псевдонимы). Например: template<int> // Идея: int_exact_trait<N>::type тип в точности из N бит struct int_exact_traits { typedef int type; }; template<> struct int_exact_traits<8> { typedef char type; }; template<> struct int_exact_traits<16> { typedef char[2] type; }; //... // Создаем синоним для более удобного использования template<int N> using int_exact = typename int_exact_traits<N>::type; // int_exact<8> является целочисленной переменной из 8 бит int_exact<8> a = 7;
Помимо использования синонимов типов совместно с шаблонами, они могут использоваться в качестве альтернативного (и, ИМО более удачного) синтаксиса для синонимов обычных типов:
См. также:
Variadic Templates Необходимо решить следующие задачи: · Как создать класс с 1, 2, 3, 4, 5, 6, 7, 8, 9, … инициализаторами? · Как избежать создания объекта по частям с последующим копированием результата? · Как создать кортеж (tuple)? Последний вопрос является ключевым: подумайте о создании кортежей! Если можно создать и использовать обобщенные кортежи, то все остальные вопросы уйдут сами собой. Вот пример (из «Короткого введения в Variadic templates» (см. ссылки)) реализации обобщенной, строго типизированной версии функции printf(). Наверное, лучше использовать boost::format, но давайте посмотрим на пример: const string pi = "pi"; const char* m = "The value of %s is about %g (unless you live in %s).\n"; printf(m, pi, 3.14159, "Indiana");
В простейшем случае функция printf() не содержит никаких дополнительных аргументов помимо строки формата, поэтому вначале обрабатываем этот случай: void printf(const char* s) { while (s && *s) { // Нужно удостовериться, что нет других аргументов // %% представляет символ % внутри строки формата if (*s=='%' && *++s!='%') throw runtime_error("invalid format: missing arguments"); std::cout << *s++; } }
Этот случай готов и нам нужно разобраться с функцией printf() с несколькими аргументами: // Обратите внимание на "..." template<typename T, typename... Args> // Обратите внимание на "..." void printf(const char* s, T value, Args... args) { while (s && *s) { // Формат указан (сам формат нам не важен) if (*s=='%' && *++s!='%') { // используем первый аргумент, не являющийся форматом std::cout << value; // "достаем" первый аргумент return printf(++s, args...); } std::cout << *s++; } throw std::runtime error("extra arguments provided to printf"); }
Этот код просто «достает» первый аргумент, не являющийся форматной строкой, и затем вызывает себя рекурсивно. Когда таких аргументов больше не останется, будет вызвана первая (более простая) версия метода printf (). Это довольно стандартная техника из области функционального программирования, применяемая во время компиляции. Обратите внимание, как перегруженный оператор << заменяет использование (потенциально ошибочной) «подсказки» (“hint”) в спецификаторе формата.
Тип Args… определяет так называемую «группу параметров» (“parameter pack”). По сути, это последовательность пар тип/значение, из которых вы можете «доставать» аргументы, начиная с первого. При вызове функции printf() с одним аргументом, будет выбран первый метод (printf(const char*)). При вызове функции printf() с двумя или более аргументами, будет выбран второй метод (printf(const char*, T value, Args… args)), с первым параметром s, вторым – value, и оставшиеся параметры (если они есть) будут запакованы в группу параметров args, для последующего использования. При вызове: printf(++s, args...);
Группа параметров args сдвигается на один, и следующий параметр может быть обработан в виде value. И так продолжается до тех пор, пока args не станет пустым (и будет вызвана первая версия метода printf()). Если вы знакомы с функциональным программированием, то можете подумать, что это немного необычная запись для довольно стандартной техники. Если вы не знакомы с функциональным программированием, то вот несколько технических примеров, способных в этом помочь в ней разобраться. Прежде всего, мы можем объявить и использовать простую шаблонную функцию с переменным числом аргументов (аналогичную функции printf(), о которой шла речь выше): // Шаблонная функция с переменным числом аргументов // (т.е. функция, способная принимать произвольное количество // аргументов произвольного типа) template<class... Types> void f(Types... args); f(); // OK: args не содержит аргументов f(1); // OK: args содержит один аргумент: int f(2, 1.0); // OK: args содержит 2 аргумента: int и double
Мы можем создать шаблонный тип с переменным числом аргументов: template<typename Head, typename... Tail> class tuple<Head, Tail...> : private tuple<Tail...> { // Используем рекурсию // По сути, кортеж (tuple) содержит голову (первую пару (тип/значение) // и наследует от кортежа с хвостом (остальные пары тип/значение). // Обратите внимание, что тип зашит в типе, а не хранится в виде данных
typedef tuple<Tail...> inherited; public: tuple() { } // Значение по умолчанию: пустой кортеж // Создаем кортеж по независимым аргументам: tuple(typename add_const_reference<Head>::type v, typename add_const_reference<Tail>::type... vtail) : m_head(v), inherited(vtail...) { } // Создаем кортеж по другому кортежу: template<typename... VValues> tuple(const tuple<VValues...>& other) : m_head(other.head()), inherited(other.tail()) { } template<typename... VValues> tuple& operator=(const tuple<VValues...>& other) // присваивание { m_head = other.head(); tail() = other.tail(); return *this; } typename add_reference<Head>::type head() { return m_head; } typename add_reference<const Head>::type head() const { return m_head; } inherited& tail() { return *this; } const inherited& tail() const { return *this; } protected: Head m_head; }
С таким определением мы можем создавать кортежи (а также копировать и работать с ними): tuple<string,vector,double> tt("hello",{1,2,3,4},1.2); string h = tt.head(); // "hello" tuple<vector<int>,double> t2 = tt.tail(); // {{1,2,3,4},1.2};
Поскольку указывать все эти типы довольно утомительно, то обычно, мы можем вывести аргументы типов, например, с помощью метода стандартной библиотеки make_tuple(): // это несколько упрощенное определение (см. раздел стандарта 20.5.2.2) template<class... Types> tuple<Types...> make_tuple(Types&&... t) { return tuple<Types...>(t...); } string s = "Hello"; vector<int> v = {1,22,3,4,5}; auto x = make_tuple(s,v,1.2);
См. также:
Унифицированый синтаксис и семантика инициализации В С++ существует несколько способов инициализации объекта в зависимости от его типа и контекста инициализации. В случае неправильного использования, ошибка может быть неожиданной и непонятной. Давайте рассмотрим следующие примеры: // ok: инициализация переменной массива string a[] = { "foo", " bar" }; // ошибка: инициализатор списка для неагрегированного вектора vector<string> v = { "foo", " bar" }; void f(string a[]); // ошибка синтаксиса: блок в качестве аргумента f({ "foo", " bar" });
и // "стиль присваивания" int a = 2; // стиль присваивания списка int[] aa = { 2, 3 }; // инициализация "в стиле вызова функции" complex z(1,2); // инициализация "в стиле вызова функции" для // конвертации/преобразования/конструирования x = Ptr(y);
и int a(1); // определение переменной int b(); // определение функции int b(foo); // определение переменной или объявление функции
Может быть сложно запомнить все правила инициализации и выбрать правильный. В С++11 эта проблема решается с помощью списка инициализации {}:
X x1 = X{1,2}; X x2 = {1,2}; // знак равно (=) необязателен X x3{1,2}; X* p = new X{1,2}; struct D: X { D(int x, int y):X{x,y} { /*... */ }; }; struct S { int a[3]; S(int x, int y, int z):a{x,y,z} { /*... */ }; // решение старой проблемы };
Очень важно, что X{a} создает одно и то же значение не зависимо от контекста, так что {}- инициализация приводит к одному и тому же результату везде, где она применима. Например: X* p = new X{a}; z = X{a}; // используется преобразование типов f({a}); // аргумент функции (типа X) return {a}; // возвращаемое значение функции (функция возвращает X)
См. также:
Rvalue ссылки Разница между lvalue (которые могут использоваться слева от оператора присваивания) и rvalue значениями (которые могут использоваться справа от оператора присваивания) ведут свое начало от Кристофера Страчи (Christopher Strachey) («папы» дальнего родственника С++ под названием CPL и его денотационных семантик). В языке С++ неконстантная ссылка может быть связана с lvalue, константная ссылка – с lvalue или rvalue, но не существует ничего, что может быть связано с неконстантным rvalue значением. Это сделано для защиты от изменения значений временных объектов, которые будут уничтожены до того, как новым значением можно будет воспользоваться. Например: void incr(int& a) { ++a; } int i = 0; incr(i); // i буде равняться 1 incr(0); // ошибка: 0 не является lvalue
Если бы вызов incr(0) был бы разрешен, то тогда либо будет увеличено значение временной переменной, которое никто не сможет увидеть, либо, что еще хуже, 0 станет равен 1. Последний вариант звучит глупо, но подобный баг был в ранних версиях компилятора Fortran, который позволял втихую изменить ячейку памяти, хранившую 0. Пока все хорошо, но давайте рассмотрим следующий пример: // "старый добрый обмен значениями" template<class T> swap(T& a, T& b) { T tmp(a); // теперь у нас есть две копии a a = b; // теперь у нас есть две копии b b = tmp; // теперь у нас есть две копии tmp (aka a) }
Если для типа T копирование элементов является дорогой операцией, как например, для типов string или vector, операция swap также становится достаточно дорогой операцией (поэтому в стандартной библиотеке у нас есть специализированные версии методов swap для строки и вектора). Обратите внимание на интересный момент: мы вообще не хотим делать никаких копий. Мы просто хотим поменять значения a, b и tmp. Для перемещения, а не копирования аргументов в С++11 мы можем определить «конструкторы перемещения» (move constructors) и «операторы перемещения» (move assignments): template<class T> class vector { //... vector(const vector&); // конструктор копирования vector(vector&&); // конструктор перемещения vector& operator=(const vector&); // обычное присваивание vector& operator=(vector&&); // оператор перемещения }; // обратите внимание: конструктор и оператор перемещения // принимают неконстантные && // они могут (и обычно делают) изменяют свои аргументы
&& означает “rvalue-ссылку”. rvalue-ссылки могут быть связаны только с rvalue (но не с lvalue). X a; X f(); X& r1 = a; // связывает r1 с a (lvalue) X& r2 = f(); // ошибка: f() возвращает rvalue; X&& rr1 = f(); // ok: связывает rr1 с временным объектом X&& rr2 = a; // ошибка: a – это lvalue
Идея семантики перемещения заключается в том, что вместо создания копии, мы можем просто взять значение из источника и заменить его дешевым значением по умолчанию. Например, для выражения s1=s2 с поддержкой перемещения, символы строки s2 скопированы не будут; вместо этого, строка s1 будет рассматривать символы строки s2, как свои собственные и удалит свои исходные данные (возможно передаст их строке s2, которая вскоре будет удалена). Откуда мы знаем, что перемещение данных из источника является безопасным? Мы говорим об этом компилятору: template<class T> void swap(T& a, T& b) // "идеальный обмен значениями" (почти) { T tmp = move(a); // может сделать a недействительным a = move(b); // может сделать b недействительным b = move(tmp); // может сделать tmp недействительным }
move(x) означает «вы можете рассматривать x в качестве rvalue». Возможно, было бы лучше, если бы move() назывался rval(), но move() уже используется многие годы. Шаблонная функция move() может быть написана на С++11 (см. "Brief introduction to rvalue references") с помощью rvalue-ссылок. Rvalue-ссылки могут использоваться для создания идеального механизма перенаправления (forwarding). В стандартной библиотеке С++11 во все контейнеры добавлены конструкторы и операторы перемещения. Также операции добавления новых элементов, такие как insert() и push_back() содержат версии, принимающие rvalue-ссылки. Конечным результатом является то, что производительность стандартных контейнеров и алгоритмов тихо (без вмешательства пользователя) была улучшена за счет уменьшения необходимости копирования. См. также:
Объединения В С++98 (как и в более ранних версиях языка С++), члены с пользовательским конструктором, деструктором или оператором присваивания не могли использоваться в объединениях (union): union U { int m1; // ошибка (глупая): complex содержит конструктор complex<double> m2; // ошибка (не глупая): string обладает сложным инвариантом // поддерживается ctor, copy и dtor string m3; };
В частности: // какой конструктор вызывать (и вызывать ли вообще)? U u; // присваивание члену с типом int u.m1 = 1; // беда: чтение строки string s = u.m3;
Очевидно, некорректно писать один член, а затем читать другой; но, несмотря на это, пользователи постоянно это делают (обычно по ошибке). В С++11, ограничения, накладываемые на объединения, изменены для поддержки большего количества типов; в частности, членам объединений позволяется иметь конструкторы и деструкторы. Стандарт также добавляет ограничения, чтобы уменьшить количество ошибок в наиболее гибких вариантах использования объединений, поощряя создание размеченных объединений (discriminated unions). Типы членов объединений ограничены следующим образом:
Например: union U1 { int m1; complex<double> m2; // ok }; union U2 { int m1; string m3; // ok };
Может показаться, что это решение будет приводить к ошибкам, но благодаря новым ограничениям это не так. В частности: // ok U1 u; // ok: присваивание члена типа complex u.m2 = {1,2}; // ошибка: деструктор string приводит к удалению деструктора U U2 u2; // ошибка: конструктор копирования string приводит к удалению // конструктора копирования U U2 u3 = u2;
По сути, объединение U2 является бесполезным, пока оно не будет включено в структуру, которая отслеживает, какой из членов объединения используется в данный момент. Т.е. для создания размеченных объединений типа: // Три альтернативные реализации, использующих объединения class Widget { private: // дискриминант enum class Tag { point, number, text } type; // представление union { // point содержит конструктор point p; int i; // string содержит конструктор по умолчанию, // операции копирования и деструктор string s; }; //... // необходимо для варианта, использующего string widget& operator=(const widget& w) { if (type==Tag::text && w.type==Tag::text) { // обычное присваивание членов типа string s = w.s; return *this; } if (type==Tag::text) s.~string(); // удаление (явное!) switch (type==w.type) { case Tag::point: p = w.p; break; // обычное копирование case Tag::number: i = w.i; break; case Tag::text: new(&s)(w.s); break; // размещающийоператор new } type = w.type; return *this; } };
См. также: · the C++ draft section 9.5 · [N2544=08-0054] Alan Talbot, Lois Goldthwaite, Lawrence Crowl, and Jens Maurer: Unrestricted unions (Revison 2)
POD типы POD (“Plain Old Data”) могут обрабатываться, как C-структуры, т.е. копироваться с помощью memcpy(), инициализироваться с помощью memset(), и т.д. В С++98 определение POD было основано на наборе ограничений языковых конструкций, используемых при определении структуры: struct S { int a; }; // S - это POD struct SS { int a; SS(int aa): a(aa) { } }; // SS – это не POD struct SSS { virtual void f(); /*... */ };
В С++11 S и SS являются «типами со стандартным расположением в памяти» (standard layout type) (a.k.a. POD) поскольку SS не содержит никакой «магии»: конструктор никак не влияет на расположение в памяти (поэтому инициализация с помощью memcpy() является возможной), и только лишь с инициализацией с помощью memset() будут проблемы, поскольку инициализация таким образом не будет обеспечивать инвариант. Однако, SSS будет содержать указатель на таблицу виртуальных функций vptr, и не может рассматриваться как «простая старая структура данных». В С++11 для работы с разными техническими аспектами, даются следующие определения: POD-типов, простых копируемых типов и типов со стандартным расположением в памяти. Определение POD-типа является рекурсивным: · Если все члены и базовые типы являются POD-типами, то текущий тип является POD-типом · Как и ранее (детали описаны в разделе 9 [10]) o Отсутствие виртуальных функций o Отсутствие виртуальных базовых классов o Отсутствие ссылок o Отсутствие нескольких спецификаторов доступа Наиболее важный аспект POD-типов в С++11 заключается в том, что добавление или удаление конструкторов не влияет на производительность или расположение объектов в памяти. См. также:
Сырые строковые литералы Во многих случаях, например, при написании регулярных выражений для стандартной библиотеки regex, тот факт, что обратный слэш (\) необходимо экранировать – немного раздражает (поскольку в регулярных выражениях обратный слэш является специальным символом). Давайте посмотрим, как будет выглядеть паттерн для поиска двух слов, разделенных обратным слешем (\w\\\w): // я надеюсь, что не ошибся string s = "\\w\\\\\\w";
Обратите внимание, что обратный слэш в регулярном выражении представлен двумя слэшами. По сути, «сырой строковый литерал» - это строковый литерал, в котором обратный слэш является обратным слэшем, так что наш пример будет выглядеть так: // я практически уверен, что не ошибся string s = R"(\w\\\w)";
Исходное предложение по добавлению сырых литералов в стандарт содержало следующий убедительный пример: // Не является ли ошибкой 5 обратных слэшей? // Даже эксперты легко могут допустить ошибку. "('(?:[^\\\\']|\\\\.)*'|\"(?:[^\\\\\"]|\\\\.)*\")|"
R”(…)” является более тяжеловесной записью по сравнению с «простой» записью вида “…”, но в любом случае без экранирующих символов нам «нужно что-то еще». А как в сырую строку добавить кавычки? Очень легко, если они не идут перед символом открывающей скобки ‘ ) ’: R"("quoted string")" // строка содержит "quoted string"
Так как нам записать в сырой строке )”? К счастью эта проблема возникает не часто, но “(…)” является всего лишь ограничительной парой по умолчанию. Мы можем добавить ограничители до и после (…) в “(…)”. Например: // строка содержит "quoted string containing the usual terminator (")" R"***("quoted string containing the usual terminator (")")***"
Последовательность символов после ) должна быть идентичной последовательности до (. Это позволит справиться с паттернами (практически) любой сложности. Перед символом R может находиться префикс, обозначающий кодировку символов: u8, u, U или L. Например, u8R”(fdfdfa)” представляет литерал в кодировке UTF-8. См. также:
Пользовательские литералы В языке С++ существуют литералы для разных встроенных типов (2.14 Literals): 123 // int 1.2 // double 1.2F // float 'a' // char 1ULL // unsigned long long 0xD0 // unsigned int в шестнадцатеричном формате "as" // string
Однако в языке С++98 нельзя определить литералы для пользовательских типов. Это раздражает, а также нарушает принцип, что поддержка пользовательских и встроенных типов должна быть аналогичной. В частности, многие разработчики просили поддержку следующих литералов: "Hi!"s // строка, но не “массив символов, // оканчивающийся нулем” 1.2i // мнимое число 123.4567891234df // decimal floating point (IBM) 101010111000101b // двоичное число 123s // секунды 123.56km // не мили! (единицы измерения) 1234567890123456789012345678901234567890x // расширенная точность
С++11 поддерживает «пользовательские литералы» (user –defined literals) с помощью литеральных операторов (literal operators), которые задают соответствие литералов с определенным суффиксом для определенного пользовательского типа. Например: // литерал для определения мнимого числа constexpr complex<double> operator "" i(long double d) { return {0,d}; // complex – это тип литерала } // литерал дляstd::string std::string operator""s (const char* p, size_t n) { return string(p,n); // требуется динамическое выделение памяти }
Обратите внимание на использование constexpr для вычисления выражения во время компиляции. При наличии указанных операторов, мы можем написать следующее: template<class T> void f(const T&); f("Hello"); // передаем указатель на char* f("Hello"s); // передаем объект string (из 5 символов) f("Hello\n"s); // передаем объект string (из 6 символов) auto z = 2+1i; // complex(2,1)
Основная идея (реализации) этой возможности заключается в том, что после парсинга потенциального литерала, компилятор всегда проверяет суффикс. Механизм пользовательских литералов позволяет пользователю указать новый суффикс и что будет выполнено для литерала, расположенного до этого суффикса. Пользователь не может переопределить поведение встроенных литералов или расширить синтаксис литералов. Литеральный оператор может принимать значение в кавычках (в качестве строки) или без них. Для использования литерала без кавычек достаточно определить оператор, принимающий единственный аргумент типа const char*: Bignum operator"" x(const char* p) { return Bignum(p); } void f(Bignum); f(1234567890123456789012345678901234567890x);
В operator”” x() передается С-строка вида "1234567890123456789012345678901234567890". Обратите внимание, что мы не преобразуем явно это числовое значение в строку. Пользовательские литералы можно определить для одного из четырех типов литералов: · целочисленный литерал: литеральный оператор принимает единственный параметр типа unsigned long longили const char*. · значение с плавающей точкой: литеральный оператор принимает единственный параметр типа long double или const char*. · строковый литерал определяется литеральным оператором, принимающим пару аргументов (const char*, size_t). · символьный литерал определяется литеральным оператором, принимающим единственный параметр типа char. Обратите внимание, что вы не можете определить литеральный оператор для строкового литерала, принимающий только параметр типа const char* (без размера). Например: // предупреждение: этот код будет работать не так, как вы ожидаете string operator"" S(const char* p); "one two"S; // ошибка: литеральный оператор не найден
Причина такого поведения заключается в том, что практически всегда, когда мы хотим определить «еще один тип строки» нам нужно знать количество символов. Суффиксы должны быть короткими (например, s для строки, i для комплексных чисел, m для метров, x для расширенных целочисленных типов), что легко может приводить к коллизиям. Для предотвращения коллизий следует использовать пространства имен: namespace Numerics { //... class Bignum { /*... */ }; namespace literals { operator"" X(char const*); } } using namespace Numerics::literals;
См. также:
Атрибуты «Атрибуты» являются стандартным механизмом, который должен навести порядок в том огромном наборе возможностей и/или компиляторно-зависимых механизмов добавления специальной информации к исходному коду (например, __attribute__, __declspec и #pragma). Атрибуты С++11 влияют непосредственно на сущность, стоящую перед ними: этим они отличаются от обычных синтаксических конструкций языка. Например: // f() никогда не возвращает управление void f [[ noreturn ]] () { throw "error"; // OK } // подсказка оптимизатору struct foo* f [[carries_dependency]] (int i); int* g(int* x, int* y [[carries_dependency]]);
Как видите, атрибуты помещаются в двойные квадратные скобки: [[ … ]]. noreturn и carries_dependency являются парой атрибутов, определенных в стандарте. Существует разумное опасение, что атрибуты будут использоваться для разработки диалектов языка. Рекомендуется использовать атрибуты таким образом, чтобы они не меняли семантики программы, но могли помочь в поиске ошибок (например, [[noreturn]]) или помочь в оптимизации (например, [[carries_dependency]]). Одна из причин появления атрибутов – это улучшение поддержки OpenMP. Например: for [[omp::parallel()]] (int i=0; i<v.size(); ++i) { //... }
Как показано выше, атрибуты могут квалифицироваться пространством имен. См. также:
Лямбда-выражения Лямбда-выражение – это механизм определения объекта-функции. Основная цель лямбда-выражения заключается в определении некоторого действия, выполняемого некоторой функцией. Например: vector<int> v = {50, -10, 20, -30}; // сортировка по умолчанию std::sort(v.begin(), v.end()); // теперь v должен содержать { -30, -10, 20, 50 } // сортируем по абсолютному значению: std::sort(v.begin(), v.end(), [](int a, int b) { return abs(a)<abs(b); }); // теперь v должен содержать { -10, 20, -30, 50 }
Аргумент [](int a, int b) {return abs(a)<abs(b); } – это «лямбда» («лямбда-выражение» или «лямбда-функция»), определяющая операцию, которая для двух целочисленных аргументов a и b возвращает результат сравнения их абсолютных значений. Лямбда-выражение может получить доступ к локальным переменным из области видимости, в которой оно используется. Например: void f(vector<Record>& v) { vector<int> indices(v.size()); int count = 0; generate(indices.begin(),indices.end(),[&count](){ return ++count; }); // сортируеминдексы в порядке, определяемым полем name записей:
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|