Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Enum class – строготипизированные перечисления с дополнительной областью видимости




Auto – вывод типа из инициализатора

Рассмотрим следующий пример:

auto x = 7;

В данном случае тип переменной x будет int, потому что именно такой тип имеет ее инициализатор. В общем случае мы можем написать:

auto x = expression;

И тип переменной x будет равен типу значения, полученному в результате вычисления «выражения».

Ключевое слово auto для вывода типа переменной из ее инициализатора, наиболее полезно, когда точный тип выражения не известен, либо сложен в написании. Рассмотрим пример:

template<class T> void printall(const vector<T>& v) { for (auto p = v.begin(); p!=v.end(); ++p) cout << *p << "\n"; }

 

В С++98, вам бы пришлось писать:

template<class T> void printall(const vector<T>& v) { for (typename vector<T>::const_iterator p = v.begin(); p!=v.end(); ++p) cout << *p << "\n"; }

 

Написать код без использования ключевого слова auto может быть особенно сложно, когда тип переменной тесно связан с типами аргументов шаблона. Например:

template<class T, class U> void multiply(const vector<T>& vt, const vector<U>& vu) { //... auto tmp = vt[i]*vu[i]; //... }

 

Тип переменной tmp должен зависеть от результата умножения T на U, но человеку может быть очень сложно определить конкретный результирующий тип, хотя, конечно же, компилятор легко справится с этой задачей, после того как будут известны конкретные типы T и U.

Отличительная особенность этой возможности состоит в том, что это была самая первая предложенная и реализованная возможность: у меня была ее реализация еще на Cfront в далеком 1984, но мне пришлось от нее избавиться из-за проблем совместимости с языком С. Эта проблема исчезла после того, как С++98 и С99 решили избавиться от использования int, в качестве неявного типа; т.е. оба языка теперь требуют, чтобы в объявлении каждой переменной или функции использовался явный тип. Старое значение ключевого слова auto (“это локальная переменная”) теперь недопустимо. Некоторые члены комитета просмотрели миллионы строк кода в поисках корректного использования, но большая часть касалась тестов или это явно были баги.

Поскольку эта возможность предназначена, прежде всего для упрощения кодирования, auto никак не влияет на спецификацию стандартной библиотеки.

См. также:

· Черновик С++, раздел 7.1.6.2, 7.1.6.4, 8.3.5 (для типов возвращаемых значений

· [N1984=06-0054] Jaakko Jarvi, Bjarne Stroustrup, and Gabriel Dos Reis: Deducing the type of variable from its initializer expression (revision 4).

 

Range-for оператор

Range-for оператор позволяет итерировать по «диапазону» (range), что позволяет «пройтись» по любой STL последовательности, заданной методами begin() и end(). Все стандартные контейнеры могут быть использованы в качестве «диапазона», в том числе std::string, список инициализаторов, массив и любой другой класс, для которого вы определите методы begin() и end(), например, istream. Например:

void f(vector<double>& v) { for (auto x: v) cout << x << '\n'; for (auto& x: v) ++x; // использование ссылки дает возможность изменять значение }

 

Вы можете прочитать этот код таким образом: «для всех x в v», перебрать все элементы, начиная с v.begin() и заканчивая v.end(). Вот еще один пример:

for (const auto x: { 1,2,3,5,8,13,21,34 }) cout << x << '\n';

 

Методы begin() и end() могут быть функциями членами и вызываться в форме x.begin() или свободными функциями, вызываемыми в форме begin(x). При этом приоритет функций-членов – выше.

См. также:

· the C++ draft section 6.5.4

· [N2243==07-0103] Thorsten Ottosen: Wording for range-based for-loop (revision 2).

  • [N3257=11-0027 ] Jonathan Wakely and Bjarne Stroustrup: Range-based for statements and ADL (Был выбран 5-й вариант).

 

Правые угловые скобки

Давайте рассмотрим следующий код:

list<vector<string>> lvs;

 

С точки зрения С++98 приведенный код некорректен, поскольку между двумя закрывающими угловыми скобами (>) нет пробела. С++11 рассматривает подобную комбинацию угловых скобок как корректный терминатор списка из двух аргументов шаблона.

А почему это вообще было проблемой? Компилятор состоит из нескольких этапов анализа. Вот самая простая модель:

· Лексический анализ (создание лексем из символов).

· Синтаксический анализ (проверка грамматики).

· Проверка типов (поиск имен типов и выражений).

В теории, а иногда и на практике, эти этапы четко разделены, так что лексический анализатор, определяющий, что “>>” является маркером (который обычно означает правый сдвиг или ввод), понятия не имеет о его значении; в частности, он не имеет ни малейшего понятия, ни о шаблонах, ни о вложенном списке аргументов шаблона. Однако чтобы сделать этот пример «корректным», эти три этапа должны как-то взаимодействовать. Ключевое наблюдение, которое привело к решению этой проблемы заключалось в том, что компиляторы С++ выдавали подходящее сообщение об ошибке, а значит уже умели выполнять весь необходимый анализ.

См. также:

· Раздел стандарта???

· [N1757==05-0017] Daveed Vandevoorde: revised right angle brackets proposal (revision 2).

 

Управление поведением по умолчанию: default и delete

Сейчас стандартная идиома «запрещения копирования» может быть явно выражена следующим образом:

class X {

//...

X& operator=(const X&) = delete; // Запрет копирования

X(const X&) = delete;

};

 

И наоборот, мы можем явно сказать о том, что хотим использовать поведение копирования по умолчанию:

class Y {

//...

Y& operator=(const Y&) = default; // Семантика копирования по умолчанию

Y(const Y&) = default;

}

};

 

Явная спецификация поведения по умолчанию является избыточной. Однако являются редкостью комментарии для операций копирования и (что еще хуже) явно определенные пользователем операции копирования, которые должны вести себя аналогично операциям по умолчанию. Реализация поведения по умолчанию этих операций компилятором обычно проще, менее подвержено ошибкам и часто приводит к лучшему объектному коду.

Ключевое слово “default” может использоваться с любой функцией для которой компилятор может реализовать поведение по умолчанию. Ключевое слово “delete” может быть использовано с любой функцией. Например, так вы можете запретить нежелательное преобразование типов:

struct Z {

//...

// может быть инициализирован с параметром типа long long

Z(long long);

// но ни с чем другим

Z(long) = delete;

};

 

См. также:

· Раздел черновика стандарта С++???

  • [N1717==04-0157] (Francis Glassborow and Lois Goldthwaite: explicit class and default definitions (an early proposal).
  • Bjarne Stroustrup: Control of class defaults (a dead end).
  • [N2326==07-0186] Lawrence Crowl: Defaulted and Deleted Functions.
  • [N3174=100164] B. Stroustrup: To move or not to move. An analysis of problems related to generated copy and move operations. Approved.

 

Управление поведением по умолчанию: копирование и перемещение

По умолчанию класс содержит 5 операций:

· Оператор копирования

· Конструктор копирования

· Оператор перемещения (move assignment)

· Конструктор перемещения

· Деструктор

При объявлении одной из этих операций вы должны проанализировать их все, и определить явно (или указать компилятору использовать семантику по умолчанию) только те, которые вам действительно необходимы. Рассматривайте копирование, перемещение и уничтожение, как тесно связанные, а не отдельные операции, которые можно спокойно комбинировать друг с другом; вы можете использовать любые комбинации этих операций, но только некоторые из них будут иметь смысл семантически.

Если пользователь явно определил любую из операций: деструктор, операторы перемещения или копирования (объявил, определил, любо воспользовался ключевыми словами delete или default), то по умолчанию, операции перемещения сгенерированы не будут. Если же пользователь явно определил любую из операций: операторы перемещения, копирования или деструктор (объявил, определил, любо воспользовался ключевыми словами delete или default), то неоговоренные операции копирования будут определены автоматически с поведением по умолчанию. Однако такое поведение является устаревшим (deprecated), так что рассчитывать на него не стоит. Например:

class X1 {

X1& operator=(const X1&) = delete; // запрещаем копирование

};

 

Это также запрещает перемещение (moving) класса экземпляров X1. Конструктор копирования разрешен, но является устаревшим (deprecated).

class X2 {

X2& operator=(const X2&) = default;

};

 

Это объявление также явно запрещает перемещение экземпляров класса X2. Конструктор копирования разрешен, но является устаревшим (deprecated).

class X3 {

X3& operator=(X3&&) = delete; // Запрещаем перемещение

}

 

Это объявление также запрещает копирование экземпляров класса X3.

class X4 { ~X4() = delete; // Запрещаем деструктор }

 

Это объявление также запрещает перемещение объектов класса X4. Копирование разрешено, но является устаревшим.

Я очень рекомендую при определении одной из пяти этих функций, явно определять и все остальные. Например:

template<class T> class Handle { T* p; public: Handle(T* pp): p{pp} {} // пользовательский деструктор: запрещается неявное копирование и перемещение ~Handle() { delete p; } // Передача владения Handle(Handle&& h):p{h.p} { h.p=nullptr; }; // Передача владения Handle& operator=(Handle&& h) { delete p; p=h.p; h.p=nullptr; } // Копирование запрещено Handle(const Handle&) = delete; Handle& operator=(const Handle&) = delete; //... };

 

См. также:

  • the C++ draft section???
  • [N2326==07-0186] Lawrence Crowl: Defaulted and Deleted Functions.
  • [N3174=100164] B. Stroustrup: To move or not to move. An analysis of problems related to generated copy and move operations. Approved.

 

enum class – строготипизированные перечисления с дополнительной областью видимости

enum class («новые перечисления» или «строгие перечисления» решает три проблемы обычных перечислений языка С++:

· Стандартные перечисления (enums) могут неявно преобразовываться к int, что может приводить к ошибкам, если кто-то не хочет, чтобы перечисления вели себя как целые числа.

· Стандартные перечисления экспортируют свои значения в окружающую (surrounding) область видимости (scope), что приводит к коллизиям имен.

· Невозможно указать тип, лежащий в основе стандартных перечислений (underlying type), что приводит к непониманию, проблемам совместимости и делает предварительное объявление (forward declaration) невозможным.

enum class («строгие перечисления) являются строготипизированными и с дополнительной областью видимости (scoped):

enum Alert { green, yellow, election, red }; // обычное перечисление // строготипизированное перечисление с дополнительной областью видимости // имена перечисления не экспортируются в окружающую область видимости // отсутствует неявное преобразования имен перечисления к int enum class Color { red, blue }; enum class TrafficLight { red, yellow, green }; Alert a = 7; // ошибка (как обычно C++) Color c = 7; // ошибка: нет преобразования int->Color int a2 = red; // ОК: преобразование Alert->int int a3 = Alert::red; // ошибка в C++98; ОК в C++11 int a4 = blue; // ошибка: blue не объявлено в текущей области видимости int a5 = Color::blue; // ошибка: нет преобразования Color->int Color a6 = Color::blue; // ОК

 

Как показано выше, стандартные перечисления работают как и раньше, помимо этого появилась возможность квалифицировать имя элемента перечисления именем самого перечисления (int a3 = Alert::red; в примере выше).

Новые перечисления являются «классом перечисления» (enum class), поскольку они объединяют поведение обыкновенных перечислений (являются именованными значениями) с некоторыми возможностями классов (наличие области видимости и отсутствие преобразований).

Возможность явного указания типа, лежащего в основе перечисления, упрощает взаимодействие между различными платформами и гарантирует размер перечислений:

enum class Color: char { red, blue }; // компактное представление

// типом по умолчанию являетсяint

enum class TrafficLight { red, yellow, green };

// А какой размер E?

// (вычисляется согласно старым правилам;

// т.е. зависит от реализации "implementation defined")

enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U };

// теперь мы можем задать размер явно

enum EE: unsigned long { EE1 = 1, EE2 = 2, EEbig = 0xFFFFFFF0U };

 

Это также позволяет предварительное объявление (forward declaration) перечислений:

enum class Color_code: char; // (предварительное) объявление void foobar(Color_code* p); // использование этого объявления //... enum class Color_code: char { red, yellow, green, blue }; // определение

 

Типом, лежащим в основе перечисления должен быть знаковый или беззнаковый целочисленный тип; по умолчанию, это int.

В стандартной библиотеке классы перечисления используются:

· Для отражения системных кодов ошибок: enum class errc;

· Для определения безопасности указателей (pointer safety): enum class pointer_safety { relaxed, preferred, strict };

· Для ошибок потоков ввода-вывода: enum class io_errc { stream = 1 };

· Для обработки ошибок при асинхронном взаимодействии: enum class future_errc { broken_promise, future_already_retrieved, promise_already_satisfied };

Для некоторых из этих типов объявлены операторы, например оператор ==.

См. также:

  • the C++ draft section 7.2
  • [N1513=03-0096] David E. Miller: Improving Enumeration Types (original enum proposal).
  • [N2347 = J16/07-0207] David E. Miller, Herb Sutter, and Bjarne Stroustrup: Strongly Typed Enums (revision 3).
  • [N2499=08-0009] Alberto Ganesh Barbati: Forward declaration of enumerations.

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...