Перейти к содержанию

Последнее обновление:2026-10-12

Семнадцать расширенных возможностей C++17

Полный обзор 17 расширенных возможностей C++17

C++17 — одна из наиболее распространённых версий на сегодняшний день; потратим время на разбор 17 важных возможностей, хотя ими все возможности этой версии не исчерпываются.

1. Параллельные алгоритмы

В C++17 появилось множество параллельных версий алгоритмов из стандартной библиотеки. Эти алгоритмы могут выполняться параллельно, поэтому на многоядерных системах они могут дать заметный прирост производительности.

Ранее я уже писал подробную статью об этой возможности, можно посмотреть здесь:

Пример:

#include <algorithm>
#include <vector>
#include <execution>
int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::sort(std::execution::par, v.begin(), v.end());
}

В этом примере std::sort выполняется параллельно, сортируя элементы вектора v в параллельном режиме.

2. If Initializers

Инициализатор в if (If Initializers) в C++17 — это возможность, позволяющая инициализировать переменные прямо в операторе if. Такой способ инициализации в определённой мере повышает читабельность и лаконичность кода.

В традиционном C++ мы обычно инициализировали переменные так:

int x;
if (condition) {
    x = 42;
} else {
    x = 24;
}

А в C++17 можно использовать инициализатор if, чтобы упростить этот процесс и сделать код более компактным:

if (bool condition = /* some condition */) {
    int x = 42;
} else {
    int x = 24;
}

В этом примере мы поместили инициализацию переменной x прямо в оператор if. Переменная condition определяется и инициализируется в операторе if, а затем доступна в его блоках. Такой способ более нагляден и лаконичен, особенно при простой условной инициализации.

3. Выведение аргументов шаблона класса (CTAD)

CTAD позволяет компилятору автоматически выводить шаблонные параметры из аргументов функции. Это упрощает использование шаблонов без явного указания шаблонных параметров.

Подробная статья об этой возможности из предыдущих выпусков:

Пример:

template <typename T>
void foo(T t) {
    // ...
}

int main() {
    foo(42);  // 编译器推导出T的类型为int
}

В этом примере при вызове foo(42) компилятор выводит, что типом T является int.

4. template <auto>

Ключевое слово шаблона введено как заполнитель для нетиповых шаблонных параметров. Оно позволяет представлять в шаблоне значение любого типа.

Пример:

template<auto value>
struct constant {
    static constexpr auto get_value() { return value; }
};

// 用法
static_assert(constant<42>::get_value() == 42);

5. std::optional и std::variant

std::optional и std::variant — два новых типа, появившихся в C++17. std::optional представляет значение, которое может как существовать, так и отсутствовать, а std::variant — типобезопасное объединение, способное хранить значения разных типов.

Пример:

#include <optional>
#include <variant>

int main() {
    std::optional<int> opt = 42;
    std::variant<int, double> var = 3.14;
}

В этом примере opt — необязательное целое, содержащее значение 42, а var — вариант, содержащий значение 3.14.

6. Свёртывающие выражения

В C++17 свёртывающие выражения (fold expressions) предоставляют лаконичный способ применять бинарную операцию к пакету параметров. Они позволяют выполнять операции вроде суммирования, умножения или конкатенации элементов пакета параметров без явной рекурсии или итерации.

Например:

#include <iostream>

template<typename T>
T sum(T t) {
    return t;
}

// 使用折叠表达式的递归情况
template<typename T, typename... Args>
T sum(T first, Args... args) {
    return first + sum(args...);
}

int main() {
    int total = sum(1, 2, 3, 4, 5);
    std::cout << "总和: " << total << std::endl;

    return 0;
}

Свёртывающее выражение (first + ... + args) в рекурсивной функции sum применяет операцию сложения к каждому элементу пакета параметров.

7. Структурированные привязки

Структурированные привязки позволяют разложить объект на составляющие его элементы — похоже на распаковку кортежа, с которой вы, возможно, сталкивались.

Статья из предыдущих выпусков:

Пример:

#include <tuple>
#include <string>
int main() {
    std::tuple<int, std::string, double> t(42, "hello", 3.14);
    auto [i, s, d] = t;  // i = 42, s = "hello", d = 3.14
}

В этом примере структурированная привязка [i, s, d] раскладывает кортеж t на составляющие его элементы.

8. Шаблонные шаблонные параметры

Например: в C++17 синтаксис template<template<class...>typename bob> struct foo {} объявляет шаблон с именем foo, который принимает шаблонный шаблонный параметр bob. Шаблонный шаблонный параметр bob сам принимает произвольное число шаблонных типовых параметров.

template <template<class...> typename bob>
struct foo {
    template<typename T>
    void bar(const bob<T>& arg) {
        std::cout << "size: " << arg.size() << std::endl;
    }
};

int main() {
    foo<std::vector> f;
    std::vector<int> vec = {1, 2, 3, 4, 5};
    f.bar(vec);

    return 0;
}

В функции main мы инстанцируем foo с std::vector, передавая его как шаблонный параметр bob. Это позволяет создать универсальную структуру foo, работающую с любым шаблоном, принимающим произвольное число типовых параметров, например std::vector, std::list или пользовательскими шаблонами.

9. Inline-переменные

C++17 позволяет определять переменные внутри определения класса как inline — это помогает уменьшить размер бинарника и, возможно, повысить производительность за счёт предотвращения дублирования переменной в нескольких единицах трансляции.

Пример:

class MyClass {
public:
    inline static int inlineVar = 42;
};

int main() {
    int localVar = MyClass::inlineVar;
}

Здесь inlineVar — inline-статическая переменная-член класса MyClass.

10. Улучшения атрибутов

C++17 предоставляет новые атрибуты и улучшает существующие, позволяя разработчикам сообщать компилятору больше информации о поведении кода.

Пример:

[[nodiscard]] int get_sum(int a, int b) {
    return a + b;
}

int main() {
    auto result = get_sum(1, 2);
    // 编译器可能会警告‘result’未使用
}

В этом примере [[nodiscard]] — атрибут, который можно применить к функции и который означает, что её возвращаемое значение не должно отбрасываться вызывающим кодом.

11. Вложенные пространства имён

C++17 усилил вариативные шаблоны за счёт свёртывающих выражений, сделав код для работы с пакетами параметров более лаконичным и выразительным.

Пример:

// 外部命名空间
namespace outer {
    // 内部命名空间
    namespace inner {
        void foo() {
            std::cout << "在内部命名空间中" << std::endl;
        }
    }
}

outer::inner::foo();

Определения вложенных пространств имён дают способ иерархически организовать код, повышая читабельность и сопровождаемость, особенно в крупных проектах. Они также помогают избегать конфликтов имён, предоставляя более структурированную иерархию пространств имён.

12. Улучшения литералов

C++17 улучшил литералы, включая усовершенствования целочисленных и плавающих литералов, а также поддержку литералов true и false.

Пример:

auto num = 123_456; // Underscore in integer literals  
auto pi = 3.1415_f; // Suffix for floating-point literals

13. constexpr Lambda

C++17 позволяет лямбда-функциям быть constexpr: если они удовлетворяют условиям, их можно использовать в контекстах, требующих вычисления на этапе компиляции, например:

constexpr auto lambda = [](int x) { return x * 2; };
static_assert(lambda(5) == 10);

В этом примере lambda — constexpr-лямбда, которая принимает целое число x в качестве параметра и возвращает удвоенное x. static_assert проверяет на этапе компиляции, что значение lambda(5) равно 10.

14. Захват *this

Захват *this в лямбде стал проще и позволяет напрямую обращаться к членам содержащего объекта.

class Foo {
    int data = 42;
public:
    auto member_lambda() {
        return [*this] { std::cout << data << std::endl; };
    }
};

// 使用
Foo f;
f.member_lambda()(); // 输出 "42"

15. Расширенные операторы if и switch

Условие в операторах if или switch теперь может быть любым выражением, а не только логическим. Это делает управление потоком выполнения более гибким — например, при использовании структурированных привязок:

if (const auto [it, inserted] = map.insert({"foo", bar}); inserted) {
    // ...
}

В этом примере оператор if проверяет, истинно ли значение переменной inserted, но условие также включает присваивание со структурированной привязкой.

16. Обобщённый цикл for по диапазону

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

for (auto it = my_container.begin(); it != my_container.end(); ++it) {
   // ...
}

В этом примере my_container может быть контейнером, использующим конечный итератор другого типа, но цикл всё равно будет работать корректно.

17. if constexpr

Эта возможность позволяет компилятору вычислять условие на этапе компиляции, делая код более универсальным. Если условие истинно, код внутри блока if включается в скомпилированный код; в противном случае он отбрасывается. Это может упростить код, убрав ненужные ветвления на этапе выполнения. Например:

if constexpr (std::is_same_v<T, int>) {
   // 用于int的特定代码
} else {
   // 用于其他类型的代码
}

В этом примере оператор if constexpr проверяет, является ли тип T типом int, и соответственно включает нужный код.

Итоги

Пока напишем столько, остальное будет изложено в следующем разделе.

Похожие темы

Комментарии