Пять сложных тем для элементов данных в C ++ 20

Работа с элементами данных и дизайном классов необходима практически для любого проекта на C ++. В этой статье будет изложено пять тем, которые, надеюсь, заинтересуют вас внутренними элементами C ++.
1. Изменение статуса агрегатов Для просмотра ссылки Войди или Зарегистрируйся
Интуитивно простой тип класса или массив следует рассматривать как “агрегированный” тип. Это означает, что мы можем инициализировать его с помощью фигурных скобок{}:
Код:
#include <iostream>
#include <array>
#include <type_traits>
#include <utility>
#include <tuple>
struct Point {
double x {0.0};
double y {0.0};
};
int main() {
std::array<int, 4> numbers { 1, 2, 3, 4 };
std::array statuses { "error", "warning", "ok" }; // CTAD
Point pt { 100.0, 100.0 };
std::pair vals { "hello", 10.5f };
std::tuple pack { 10, true, "important" };
static_assert(std::is_aggregate_v<decltype(numbers)>);
static_assert(std::is_aggregate_v<decltype(statuses)>);
static_assert(std::is_aggregate_v<decltype(pt)>);
// not an aggregate...
static_assert(!std::is_aggregate_v<decltype(vals)>);
static_assert(!std::is_aggregate_v<decltype(pack)>);
}
Для просмотра ссылки Войди
Но что такое простой тип класса? С годами определение в C ++ немного изменилось.
В настоящее время, начиная с C ++ 20, у нас есть следующее определение:
Из Для просмотра ссылки Войди
Однако, например, до C ++ 14 нестатические инициализаторы элементов данных (NSDMI или инициализация внутри класса) были запрещены. В C ++ 11 Pointкласс из предыдущего примера не был агрегатом, но это начиная с C ++ 14.Агрегат - это массив или класс с
- нет объявленных пользователем или унаследованных конструкторов,
- нет частных или защищенных прямых нестатических элементов данных,
- нет виртуальных функций и
- нет виртуальных, частных или защищенных базовых классов.
В C ++ 17 включены базовые классы, а также расширенная поддержка фигурных скобок. Теперь вы можете повторно использовать некоторые удобные агрегаты в качестве базовых классов без необходимости написания конструкторов:
Код:
#include <string>
#include <type_traits>
enum class EventType { Err, Warning, Ok};
struct Event {
EventType evt;
};
struct DataEvent : Event {
std::string msg;
};
int main() {
DataEvent hello { EventType::Ok, "hello world"};
static_assert(std::is_aggregate_v<decltype(hello)>);
}
Для просмотра ссылки Войди
Если вы скомпилируете с std=c++14флагом, вы получите:
Код:
no matching constructor for initialization of 'DataEvent'
DataEvent hello { EventType::Ok, "hello world"};
Запуск на Для просмотра ссылки Войди
У нас также есть еще несколько незначительных изменений, таких как:
- конструктор, объявленный пользователем, против определяемого пользователем или явного,
- унаследованные конструкторы
2. Нет скобок для прямой инициализации и NSDMI Для просмотра ссылки Войди или Зарегистрируйся
Давайте возьмем простой класс с элементом по умолчанию, установленным на `“пустой”:
Код:
class DataPacket {
std::string data_ {"empty"};
// ... the rest...
Код:
#include <iostream>
int main() {
std::string stars(40, '*'); // parens
std::string moreStars{40, '*'}; // <<
std::cout << stars << '\n';
std::cout << moreStars << '\n';
}
Для просмотра ссылки Войди
Если вы запустите код, вы увидите:
Код:
****************************************
(*
Это потому {40, '*'}, что преобразует 40 в символ ((используя его) ASCI-код) и передает эти два символа, std::initializer_listчтобы создать строку только из двух символов. Проблема в том, что прямая инициализация с помощью скобок (круглых скобок) не будет работать внутри объявления члена класса:
Код:
class DataPacket {
std::string data_ (40, '*'); // syntax error!
/* rest of the code*/
Код:
class DataPacket {
std::string data_ = std::string(40, '*'); // fine
/* rest of the code*/
3. Никаких вычетов для NSDMI Для просмотра ссылки Войди или Зарегистрируйся
Вы можете использовать autoдля статических переменных:
Код:
class Type {
static inline auto theMeaningOfLife = 42; // int deduced
};
Однако вы не можете использовать его в качестве нестатического члена класса:
Код:
class Type {
auto myField { 0 }; // error
auto param { 10.5f }; // error
};
Альтернативный синтаксис также терпит неудачу:
Код:
class Type {
auto myField = int { 10 };
};
Аналогично для CTAD (из C ++ 17). это прекрасно работает для staticчленов данных класса:
Код:
class Type {
static inline std::vector ints { 1, 2, 3, 4, 5 }; // deduced vector<int>
};
Однако он не работает как нестатический элемент:
Код:
class Type {
std::vector ints { 1, 2, 3, 4, 5 }; // syntax error!
};
То же самое происходит и с массивами, компилятор не может определить ни количество элементов, ни тип:
Код:
struct Wrapper {
int numbers[] = {1, 2, 3, 4}; // syntax error!
std::array nums { 0.1f, 0.2f, 0.3f }; // error...
};
4. Инициализация списка. Является ли это единообразным? Для просмотра ссылки Войди или Зарегистрируйся
Начиная с C ++ 11, у нас появился новый способ инициализации, называемый инициализацией списка {}. Иногда называется инициализацией фигурных скобок или даже равномерной инициализацией.Действительно ли это единообразно?
В большинстве мест вы можете использовать его ... и с каждым стандартом C ++ правила менее запутанны ... если у вас нет исключения.
Например:
Код:
int x0 { 78.5f }; // error, narrowing conversion
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type
auto x3{ 1, 2 }; // error: not a single element (since C++17)
auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>
auto x5{ 3 }; // decltype(x5) is int (since C++17)
Код:
std::vector<int> vec1 { 1, 2 }; // holds two values, 1 and 2
std::vector<int> vec2 ( 1, 2 ); // holds one value, 2
Некоторые резюме:
- Для просмотра ссылки Войди
или Зарегистрируйся - это безумие - известная статья, в которой перечислены восемнадцать различных форм инициализации (начиная с C ++ 14). - В пункте 7 для эффективного современного C ++ Скотт Мейерс сказал, что “ограниченная инициализация - это наиболее широко используемый синтаксис инициализации, он предотвращает сужение преобразований и невосприимчив к самому неприятному синтаксическому анализу C ++.
- Николай Джосуттис провел отличную презентацию обо всех угловых случаях: Для просмотра ссылки Войди
или Зарегистрируйся, и предлагает использовать {} - Основные рекомендации: Для просмотра ссылки Войди
или Зарегистрируйся . Исключение: для контейнеров существует традиция использования {...}для списка элементов и (...)размеров. Инициализация переменной, объявленной autoс помощью одного значения, например, {v}, , приводила к удивительным результатам до C ++ 17. Правила C ++ 17 несколько менее удивительны. - Только Для просмотра ссылки Войди
или Зарегистрируйся - предпочитает старый стиль. Это руководство было обновлено в 2015 году, поэтому многие вещи были обновлены с C ++ 17 и C ++ 20. - В Для просмотра ссылки Войди
или Зарегистрируйся - Тимур предлагает {} для всех, но если вы хотите быть уверены в вызове конструктора, тогда используйте () . As () выполняет регулярное разрешение перегрузки.
5. std::initializer_listявляется жадным Для просмотра ссылки Войди или Зарегистрируйся
Все контейнеры из стандартной библиотеки поддерживают конструкторы initializer_list. Например:
Код:
// the vector class:
constexpr vector( std::initializer_list<T> init,
const Allocator& alloc = Allocator() );
// map:
map( std::initializer_list<value_type> init,
const Compare& comp = Compare(),
const Allocator& alloc = Allocator() );
Мы можем создать наш собственный класс и сравнить это поведение:
Код:
#include <iostream>
#include <initializer_list>
struct X {
X(std::initializer_list<int> list)
: count{list.size()} { puts("X(init_list)"); }
X(size_t cnt) : count{cnt} { puts("X(cnt)"); }
X() { puts("X()"); }
size_t count {};
};
int main() {
X x;
std::cout << "x.count = " << x.count << '\n';
X y { 1 };
std::cout << "y.count = " << y.count << '\n';
X z { 1, 2, 3, 4 };
std::cout << "z.count = " << z.count << '\n';
X w ( 3 );
std::cout << "w.count = " << w.count << '\n';
}
Для просмотра ссылки Войди
XКласс определяет три конструктора, и один из них принимает initializer_list. Если мы запустим программу, вы увидите следующий вывод:
Код:
X()
x.count = 0
X(init_list)
y.count = 1
X(init_list)
z.count = 4
X(cnt)
w.count = 3
Как вы можете видеть, запись X x;вызывает конструктор по умолчанию. Аналогично, если вы пишете X x{};, компилятор не будет вызывать конструктор с пустым списком инициализаторов. Но в других случаях конструктор списка является “жадным” и будет иметь приоритет над обычным конструктором, принимающим один аргумент. Чтобы вызвать точный конструктор, вам нужно использовать прямую инициализацию с помощью скобок().