Изубриевка
- 1 year ago
- 0
- 0
C++17 (также известный как C++1z) — это название версии стандарта C++ ISO /IEC. Спецификации для C++17 были опубликованы в декабре 2017 года .
Значение константы
__cplusplus
стало
201703L
, это используется для
условной компиляции
.
Триграфы
использовались для машин с нестандартной кодировкой и/или ограниченной клавиатурой: так, в немецком
ASCII
вместо скобок
[\]{|}~
умляуты
ÄÖÜäöüß
. Ещё в конце 80-х, с распространением 8-битных кодировок, дешёвых
резиномембранных
клавиатур и качественно
локализованных
компьютеров, триграфы фактически потеряли смысл, и тридцать лет спустя были закономерно исключены
.
// Will the next line be executed????????????????/
a++; /* с триграфами эта строка закомментирована — триграф ??/ эквивалентен \ */
Язык Си был «переносимым ассемблером»: он позволял делать быстрые программы, компилирующиеся на разных компьютерах, к тому же использовал ассемблерные утилиты ( компоновщик , библиотекарь). Понятия вроде « заголовочный файл » и « единица трансляции » — отголоски тех времён.
Слово
register
изначально связано с ручной оптимизацией программы. Современные компиляторы «под капотом» делают огромное количество оптимизаций, и подобное ручное управление представляется излишним. Ещё в Си++11 слово объявили нежелательным. Слово всё ещё остаётся зарезервированным, и его могут когда-нибудь задействовать с другой целью — как в Си++11
auto
.
Операция явно небезопасна и запрещена ещё в Си++98
. Операция
--
отсутствует и так.
Заявленные исключения
void f() throw(A, B, C);
, имеющиеся, например, в
Java
, приносят больше вреда, чем пользы. Запрещены в Си++11, удалены в Си++17. Остался
throw()
как синоним для
noexcept(true)
.
В их числе
std::auto_ptr
,
std::random_shuffle
и старые функциональные адаптеры
.
Вместо них используются
unique_ptr
,
shuffle
и новые функциональные шаблоны, основанные на
function
/
bind
. Заявляется, что любой код на
auto_ptr
может быть механически преобразован в
unique_ptr
, с простым добавлением
std::move
там, где идёт передача владения.
Также удалены отдельные части
iostream
, запрещённые ещё в Си++98
.
Всего пять перегрузок, включая эту
template< class Alloc >
function( std::allocator_arg_t, const Alloc& alloc ) noexcept;
Из-за непонятной семантики и сложностей реализации их удалили без предварительного запрета .
Запрещено несколько редких возможностей стандартной библиотеки:
allocator<void>
— оказался невостребованным;
allocator
— дублируется шаблоном
allocator_traits
;
raw_storage_iterator
— не вызывает конструкторов и потому ограничен по применению;
get_temporary_buffer
— имеет неочевидные подводные камни;
is_literal_type
— бесполезен для обобщённого кода, но оставлен, пока в Си++ существует понятие «литеральный тип»;
iterator
— проще писать итераторы с нуля, чем основываться на нём;
codecvt
— на поверку работал очень плохо, комитет призвал пользоваться специализированными библиотеками;
shared_ptr::unique()
— из-за ненадёжности в многопоточной среде.
Полностью удалить обещают в Си++20.
result_of
→
invoke_result
— более простой синтаксис, основанный на выведении типов Си++11
;
bool uncaught_exception()
→
int uncaught_exceptions()
— в обработке одного исключения система может выбросить другое, так что могут «висеть» необработанными и несколько исключений. Проверить, сколько их было в конструкторе и сколько стало в деструкторе — более надёжный и «бесплатный» с точки зрения имеющихся библиотек метод определения, выбрасывать исключение из деструктора или нельзя
.
С переходом на Си11 удалены заголовочные файлы
<ccomplex>
,
<cstdalign>
,
<cstdbool>
,
<ctgmath>
. Файл
<ciso646>
не запрещён
.
Добавленный в Си++11 универсальный инициализатор
int x{};
позволяет одним синтаксисом создать объект, структуру, массив. В Си++17 уточнено: если вместо типа стоит
auto
— пользователь хочет создать один объект и никаких initializer_list не нужно.
При этом
auto x = {1, 2, 3};
продолжает создавать: с одной стороны, для совместимости с
for (auto x : {1, 2, 3})
, с другой — для одного объекта есть
auto x = 1;
.
auto x1 = { 3 }; // std::initializer_list<int>
auto x2 { 1, 2 }; // теперь ошибка
auto x3 { 3 }; // int
Функции
void
f
()
noexcept
(
true
);
и
void
f
()
noexcept
(
false
);
— теперь функции с разными типами (но не могут составлять перегруженный набор). Это позволит API требовать
callback
’и, которые не выбрасывают исключений, а также оптимизировать код под отсутствие таковых
.
В Си++11 появилась возможность создавать структуры данных, чьё выравнивание больше, чем теоретическое. Эта возможность была подхвачена операцией new .
class alignas(16) float4 {
float f[4];
};
float4 *p = new float4[1000];
Появилась перегрузка операции new с дополнительным параметром, чтобы корректно разместить в памяти чрезмерно выравненный объект.
Изменён смысл понятия prvalue: теперь это всего лишь инициализация.
В коде
SomeType a = 10;
хоть всё ещё требуется и конструктор, и операция =, гарантированно будет вызван только конструктор.
Это значит, что функции могут возвращать типы, которые нельзя копировать и перемещать.
Теперь операции
a.b
,
a->b
,
a->*b
,
a(b1, b2, b3)
,
b += a
(и аналоги для других операций),
a[b]
,
a << b
и
a >> b
вычисляются в порядке a → b, чтобы держать под контролем побочные эффекты
.
Если их вызвать как функции (например,
operator += (a, b)
), порядок остаётся неопределённым.
Существуют шаблоны, принимающие константу.
template <int N> struct Array
{
int a[N];
};
Что может быть константой N, и что не может — объявлено от противного. Константа в шаблоне не может быть указателем на поле, на временный объект, на строковый литерал, на результат
typeid
и на стандартную переменную
__func__
;
Теперь
for (auto v : x)
означает
auto __begin = begin-expr;
auto
__end = end-expr;
, допуская begin и end разных типов.
Это — база для прохода по диапазонам (ranges), работа над которыми продолжается .
Массивы std::vector и std::string имеют дело с непрерывными участками памяти. Для них ввели понятие «непрерывный итератор» . Концептуально ничего не изменилось.
Дали определения и другим понятиям — forwarding reference , default member initializer , templated entity . Это работа над концепциями Си++20.
Ранее подобное поведение определялось реализацией.
Заодно сделали «символы UTF-8», которые имеют тип
char
и могут держать коды от 0 до 127, по аналогии со строками UTF-8 — по видимому, чтобы программа меньше зависела от настроек локали на компьютере
.
Из-за неадекватной семантики метод упорядочивания «consume» устно (без отметки
[[
deprecated
]]
) запретили, призвав пользоваться методом «acquire». Работа над новой семантикой всё ещё ведётся и, возможно, запрет когда-нибудь снимут
.
В любом случае на PowerPC и ARM все загрузки автоматически будут consume , но не все — acquire , и метод consume может сберечь такты в кроссплатформенном коде .
Если
static_assert
не сработал, не всегда требуется сообщать программисту, что не так — часто он и сам может понять из констекста.
.
static_assert(sizeof(wchar_t) == 2);
Теперь можно в заголовочном файле написать
inline
const
ClassName
INSTANCE_NAME
и все cpp-файлы будут ссылаться на один объект — в отличие от
const
ClassName
INSTANCE_NAME
или
static
const
ClassName
INSTANCE_NAME
.
[[fallthrough]]
: в одном из разделов оператора
switch
мы намеренно «проваливаемся» в следующий. Возможная реализация
устройства Даффа
int n = (count + 7) / 8;
if (!count) return;
switch (count % 8) {
case 0: do { *to = *from++; [[fallthrough]];
case 7: *to = *from++; [[fallthrough]];
case 6: *to = *from++; [[fallthrough]];
case 5: *to = *from++; [[fallthrough]];
case 4: *to = *from++; [[fallthrough]];
case 3: *to = *from++; [[fallthrough]];
case 2: *to = *from++; [[fallthrough]];
case 1: *to = *from++;
} while (--n > 0);
}
[[nodiscard]]
: вызов функции как процедуры считается ошибкой — например, это «чистая» функция вроде
string::empty()
, вся работа которой заключается в возврате значения, или протокол работы с объектом требует что-то сделать с возвращённым значением, как в
unique_ptr::release()
. В более позднем стандарте
C++20
появилась возможность указать причину, почему вызов ошибочен.
class SmartPtr { // собственная реализация unique_ptr
public:
/// Передаёт управляемый объект под ручное управление
/// @return указатель на управляемый объект
[[nodiscard]] Payload* release();
};
SmartPtr p;
Payload* data = p.release(); // правильное использование умного указателя
delete data;
p.release(); // warning: ignoring return value of 'SmartPtr::release()', declared with attribute nodiscard
(void)p.release(); // так глушат предупреждение
[[maybe_unused]]
: в каком-то из режимов компиляции (
Windows
/
POSIX
, отладка/выпуск) тот или иной элемент не используется, и это не ошибка.
// Для краткости сделаем простейшую архитектуру, позволяющую UTF-32 → 16
// и копирование UTF-16 как есть, и больше ничего
// QString всегда UTF-16, а wstring зависит от ОС
template <int Sz> void append(QString& s, unsigned long ch);
// версия для Windows, wstring = UTF-16
template<> [[maybe_unused]] inline void append<2>(QString& s, unsigned long ch)
{ s.append(static_cast<uint16_t>(ch); }
// версия для POSIX, wstring = UTF-32
template<> [[maybe_unused]] void append<4>(QString& s, unsigned long ch)
{} // кодировка кодовой позиции в UTF-16, для краткости опустим
std::wstring s = L"\U0001F60E"; // смайлик в очках
QString r;
// Для краткости мы делаем точную копию и столь сложный код не нужен.
// Но бывает нужен в какой-нибудь обработке — например, разэкранировании символов.
for (auto c : s)
append<sizeof(c)>(r, c);
class ISoccerSeason { // интерфейс
public:
/// @pre обе команды участвуют в этом сезоне.
/// @return true, если будет сыгран матч между командой home на своём поле и away в гостях
/// @warning В типичном футбольном сезоне обе команды сыграют и на своём, и на чужом поле.
virtual bool doTeamsPlay([[maybe_unused]] const Team& home, [[maybe_unused]] const Team& away) const
{ return true; }
virtual ~ISoccerSeason() = default;
};
Недоработка языка Си++: в шаблонах
typename
и
class
кое-где не взаимозаменяемые
.
template<template<typename> class X> struct C; // Остаётся OK
template<template<typename> typename X> struct D; // Теперь OK
Оба ключевых слова явно объявлены взаимозаменяемыми.
Появился новый способ объявления переменных для распаковки сложных объектов, который получил название структурного связывания .
auto [place,wasInserted] = someMap.emplace(key, value);
Работает для пар, кортежей и прочих типов, где работает
std
::
get
.
Определение вложенных пространств имён:
namespace A::B {}
как сокращение для
namespace A { namespace B {} }
;
Например:
enum class TriBool {
NO,
MAYBE,
YES,
NN [[maybe_unused]],
UNSPECIFIED [[deprecated("Переименован в MAYBE")]] = MAYBE
};
constexpr int TriBool_N = static_cast<int>(TriBool::NN);
const char* triBoolNames[TriBool_N] = { "no", "maybe", "yes" };
Какой-то заявленной цели пока нет
, но это позволит разработчикам компиляторов придумать таковую — например, объявить, что элемент NN особый и его не надо присваивать переменным, обрабатывать в
switch
.
Концепция
SFINAE
позволила сделать несложный шаблон
enable_if
, который обеспечивает разную функциональность для разных типов, но даёт тяжеловесный код. В Си++17 можно упростить программу: оператор
if constexpr(expression)
инстанцирует код, если выражение в скобках истинно
.
template <class T>
constexpr T absolute(T arg) {
return arg < 0 ? -arg : arg;
}
template <class T>
constexpr auto precision_threshold = T(0.000001);
template <class T>
constexpr bool close_enough(T a, T b) {
if constexpr (is_floating_point_v<T>) // << !!
return absolute(a - b) < precision_threshold<T>;
else
return a == b;
}
В данном случае мы убеждаемся, что разница между дробными числами невелика, а целые просто проверяем на равенство.
Упакованные выражения :
template<typename... As> bool foo(As... args)
{ return (args && ...); }
Шестнадцатеричная мантисса и десятичный порядок:
0xC.68p+2, 0x1.P-126
, аналогично подстановке
%a
. Си поддерживает этот синтаксис с версии 99
.
Аналогично инициализации локальных переменных в
for
, делает код компактнее
.
if (auto it = m.find(key); it != m.end())
return it->second;
// Было
void f() {
[[rpr::kernel, rpr::target(cpu,gpu)]] // повтор
do_task();
}
// Стало
void f() {
[[using rpr: kernel, target(cpu,gpu)]]
do_task();
}
Позволяют задавать шаблонные параметры любого типа через
auto
.
template<auto X> struct B { static constexpr auto value = X; };
B<5> b1; // OK: template parameter type is int
B<'a'> b2; // OK: template parameter type is char
B<2.5> b3; // error: template parameter type cannot be double
Было:
[
self
=
*
this
]{
self
.
f
();
}
. Стало:
[
*
this
]{
f
();
}
.
enum
class
иногда применяется, чтобы сделать другой целый тип, не совместимый ни с чем. Теперь переменные этого типа можно инициализировать числами
enum class Handle : intptr_t { INVALID = 0 };
Handle h { 42 };
Handle h = 42; // запрещено
string::data
. Используется для вызова низкоуровневых строковых функций, которые принимают участок памяти определённой длины и заполняют его символами (например,
WinAPI
). До Си++11 использовался
const_cast
<char*>(x.data())
, до Си++17 —
&x.front()
.
emplace_back
одного элемента возвращает ссылку. Позволяет написать такую конструкцию:
v.emplace_back("alpha", "bravo").doSomething();
std::size(x)
,
std::begin(x)
,
std::end(x)
,
std::empty(x)
. Позволяют писать общий шаблонный код для контейнеров STL и массивов
. К тому же std::size — нужная функция, которую ранее часто писали своими силами с ошибками.
bool_constant
<
bool
B
>
=
integral_constant
<
bool
,
B
>
;
is_swappable
,
is_nothrow_swappable
,
is_swappable_with
,
is_nothrow_swappable_with
,
is_aggregate
(составной тип),
has_unique_object_representations
(тривиально копируемый объект, и любые два объекта с одинаковым значением имеют одинаковое внутреннее представление).
uninitialized_default_construct
,
uninitialized_value_construct
,
uninitialized_move
,
destroy
,
destroy_at
, а также их версии для
n
элементов.
void_t
<
T
>
=
void
. Упрощает создание
SFINAE
-шаблонов, которые можно раскрыть, если тип T существует
.
std
::
search
добавилась версия с объектом-искателем. По умолчанию существуют три искателя: простейший,
Бойер-Мур
и
Бойер-Мур-Хорспул
.
make_from_tuple
инициализирует тип T данными из кортежа.
atomic
::
is_always_lock_free
определяет, является ли атомарная переменная
неблокирующей
.
chrono
добавили функции округления вверх, вниз и до ближайшего.
map
/
set
добавили функции переброски (
merge
) и извлечения (
extract
) элементов.
shared_ptr
<
T
>::
weak_type
=
weak_ptr
<
T
>
.
struct
X
{
std
::
vector
<
X
>
data
;
};
. Крупные компиляторы давно поддерживают такое, осталось только заспецифицировать.
pair
и
tuple
.
unique_ptr
/
shared_ptr
могут работать с Си-массивами (
shared_ptr
<
string
[]
>
(
new
string
[
n
])
). В Си++14 требовалось протаскивать правильную функцию удаления (
shared_ptr
<
string
[]
>
(
new
string
[
n
],
default_delete
<
string
[]
>
()
)
).
common_type
.
Часто бывает нужно передать неизменную строку в другой участок кода, это можно сделать такими методами:
void doSmth(const char *s); // а что, если в строке нулевой символ? Да и внутренности функции становятся ошибкоопасными
void doSmth(const std::string &s); // а что, если строка — не string, и придётся выделять память?
В C++17 появился тип
string_view
— строка, имеющая только указатель и длину, без владения, управления памятью и даже без завершающего нуля — и поэтому она не имеет функции
c_str()
. Изменять можно только границы (начало/длину), но не символы. Задача программиста — сделать, чтобы объект не пережил тот буфер памяти, где хранится строка, и передача параметров — отличное применение для него. Объект
string_view
очень маленький (2·битность машины), и его стоит передавать по значению, а не по ссылке.
string_view
сам по себе является абстракцией — он абстрагируется от метода хранения строки, требуя только одно — чтобы текстовые данные были последовательными байтами в памяти. Только сложные необычные структуры (например,
строп/канат
) хранят строки вразброс. А все остальные — и
string
, и
const
char
*
, и разного рода массивы — преобразуются в
string_view
.
Есть две новые константы,
hardware_constructive_interference_size
и
hardware_destructive_interference_size
. Таким образом пользователь может избежать ложного общего доступа (destructive interference) и улучшить локальность (constructive interference).
struct keep_apart {
alignas(hardware_destructive_interference_size) atomic<int> cat;
alignas(hardware_destructive_interference_size) atomic<int> dog;
// cat далеко от dog, их можно менять из разных потоков.
};
struct together {
atomic<int> dog;
int puppy;
};
struct kennel {
//...
alignas(sizeof(together)) together pack;
//...
};
static_assert(sizeof(together) <= hardware_constructive_interference_size);
// убеждаемся, что together занимает одну строку кэша.
Теоретически обе константы должны быть одинаковыми, но для поддержки неоднородных архитектур решено было сделать две константы.
Мьютекс, позволяющий читать параллельно и писать одному
. Блокировщики для него называются
shared_lock
и
unique_lock
.
В библиотеке появились функции, так называемые deduction guides , позволяющие делать такое:
std::pair p(2, 4.5); // 1
std::vector<int> v = {1, 2, 3, 4};
std::vector x(v.begin(), v.end()); // 2
Для
и
добавились две новых функции
.
#include <iostream>
#include <map>
class Pair {
public:
int value1, value2;
Pair() : value1(0), value2(0) {}
explicit Pair(int aValue1) : value1(aValue1), value2(0) {}
Pair(int aValue1, int aValue2)
: value1(aValue1), value2(aValue2) {}
};
int main()
{
std::map<std::string, Pair> m;
// C++11
m["a"] = Pair(3, 4);
m.emplace("a", 1); // Pair создаётся всегда
// C++17
m.insert_or_assign("a", Pair(3, 4));
m.try_emplace("a", 1); // Pair создаётся когда надо
return 0;
}
Внесены в пространство имён std нестандартные математические функции:
beta
,
cyl_
bessel
_
i/j/k
,
cyl_
neumann
,
[comp_]
ellint_1/2/3
,
expint
,
hermite
,
[assoc_]
laguerre
,
[assoc_]
legendre
,
riemann_zeta
,
sph_bessel
,
sph_legendre
,
sph_neumann
. За пределами std (в
math.h
) их нет.
Из первого предложения (2010): «Мы надеемся, что принятие этого предложения даст посыл разным сообществам вычислителей, что, несмотря на расхожее поверье, Си++ тоже вполне годится для их отрасли». Тогда его не приняли. Сейчас основные производители библиотек ( Dinkumware , Boost , GCC ) уже имеют эти функции.
Также добавились вычисление
НОД
и
НОК
, функция приведения в диапазон (
clamp
)
, трёхмерная гипотенуза
hypot
(
x
,
y
,
z
)
.
Библиотека файловой системы, основанная на
boost::filesystem
, позволяет:
Появился класс
std
::
any
, способный содержать данные любого типа
. От реализаций требуется, чтобы небольшие объекты помещались в
any
без выделения памяти. Функция
any_cast
требует точного совпадения типа, и
any_cast
<
double
>
ничего не даст, если внутри объекта
int
.
std::cout << std::boolalpha;
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << std::endl;
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << std::endl;
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << std::endl;
// i: 1
// d: 3.14
// b: true
Также есть более простые
std::variant<int, bool, double>
и
std::optional<T>
.
Известный недостаток Си++: для низкоуровневого преобразования чисел в текст без выделения памяти приходится запускать тяжёлую и ненадёжную
sprintf
, а встроенное преобразование текста в число, оставшееся с Си, довольно ненадёжно.
Теперь есть встроенные локаленезависимые сверхскоростные
from_chars
и
to_chars
. Устроены они так, что не требуют (и не производят) закрывающего нуля и могут работать, например, на
string_view
. Из-за ограниченности и локаленезависимости предназначены они в первую очередь для
JSON
и
XML
, где нужна огромная скорость.
polymorphic_allocator
Структуры данных STL (
строки
,
вектора
и прочее) содержат шаблонный параметр — аллокатор памяти. Этот аллокатор работает как
концепция
обобщённого программирования, а не как интерфейс объектно-ориентированного: выделение памяти в куче и
пуле
даёт разные несовместимые типы. Класс
polymorphic_allocator
— стандартное начало для редкой задачи: в зависимости от каких-то условий, выделять память то в куче, то в пуле.
Сам по себе
polymorphic_allocator
— не интерфейс, но он связан с интерфейсом
memory_resource
.
std
::
invoke
Позволяет единообразно вызывать функции, объекты с операцией () (
функторы
) и лямбда-объекты
. Также добавились функции
is_invocable
,
is_invocable_r
,
invoke_result
.
Для 69 алгоритмов из
<
algorithm
>
,
<
numeric
>
и
<
memory
>
придуманы параллельные версии
.
clear()
писал
empty()
.