Возвращение к C++ — современный C++
С момента своего создания C++ стал одним из наиболее широко используемых языков программирования в мире. Грамотно сконструированные программы на языках C++ быстры и эффективны. Язык более гибкий, чем другие языки: он может работать на самых высоких уровнях абстракции и вниз на уровне кремния. C++ предоставляет стандартные библиотеки с высоким уровнем оптимизации. Он обеспечивает доступ к аппаратным функциям низкого уровня, чтобы максимально увеличить скорость и сократить потребление памяти. C++ может создавать практически любой вид программы: игры, драйверы устройств, HPC, облако, настольный компьютер, внедренные и мобильные приложения и многое другое. Даже библиотеки и компиляторы для других языков программирования пишутся на C++.
Одно из начальных требований для C++ — обратная совместимость с языком C. В результате программы на C++ всегда можно писать в стиле C: с необработанными указателями, массивами, символьными строками с завершающим нулем и другими функциями. Это может обеспечить высокую производительность, но также может приводить к ошибкам и увеличению сложности. Эволюция C++ концентрируется на возможностях, которые значительно снижают необходимость использования идиом в стиле C. Старые объекты C-программирования по-прежнему там, когда вам нужны. Однако в современном коде C++ их нужно меньше и меньше. Современный код на C++ проще, безопаснее, элегантнее и так же быстр, как и раньше.
В следующих разделах приводятся общие сведения об основных возможностях современного C++. Если не указано иное, перечисленные здесь функции доступны в C++ 11 и более поздних версиях. В компиляторе C++ от Майкрософт с помощью параметра /std
можно указать версию стандарта, используемую для проекта.
Ресурсы и интеллектуальные указатели
Одним из основных классов ошибок в программировании в стиле C является утечка памяти. Утечки часто возникают из-за невозможности вызвать delete
для памяти, выделенной с помощью new
. Современный C++ придерживается принципа получение ресурса есть инициализация (англ. Resource Acquisition Is Initialization (RAII)). Идея проста. Ресурсы (память кучи, дескрипторы файлов, сокеты и т. д.) должны принадлежать объекту. Этот объект создает и получает новый выделенный ресурс в конструкторе и удаляет его в его деструкторе. Принцип RAII гарантирует, что все ресурсы должным образом возвращаются операционной системе, когда объект-владелец выходит за пределы области.
Для поддержки простого внедрения принципов RAII стандартная библиотека языка C++ предоставляет три типа интеллектуальных указателей: std::unique_ptr
, std::shared_ptr
и std::weak_ptr
. Интеллектуальный указатель обрабатывает выделение и удаление памяти, которой он владеет. В следующем примере показан класс с членом-массивом, который выделяется в куче в вызове make_unique()
. Вызовы new
и delete
инкапсулированы в классе unique_ptr
. Когда объект widget
выходит из области действия, вызывается деструктор unique_ptr и освобождается память, выделенная для массива.
#include <memory>
class widget
{
private:
std::unique_ptr<int[]> data;
public:
widget(const int size) { data = std::make_unique<int[]>(size); }
void do_something() {}
};
void functionUsingWidget() {
widget w(1000000); // lifetime automatically tied to enclosing scope
// constructs w, including the w.data gadget member
// ...
w.do_something();
// ...
} // automatic destruction and deallocation for w and w.data
По возможности используйте интеллектуальный указатель для управления памятью кучи. Если необходимо явно использовать new
операторы и delete
операторы, следуйте принципу RAII. Дополнительные сведения см. в разделе Управление временем жизни и ресурсами объекта (RAII).
std::string
и std::string_view
.
Строки в стиле C — это еще один основной источник ошибок. Используя std::string
и std::wstring
, можно устранить практически все ошибки, связанные со строками в стиле C. Дополнительно вы получаете преимущества функций-членов для поиска, добавления в конец и начало и т. д. Оба эти класса оптимизированы для быстрой работы. При передаче строки в функцию, для которой требуется доступ только для чтения, в C++ 17 можно использовать std::string_view
для еще большего выигрыша в производительности.
std::vector
и другие контейнеры стандартной библиотеки
Все контейнеры стандартной библиотеки следуют принципу RAII. Они предоставляют итераторы для безопасного обхода элементов. И они хорошо оптимизированы для повышения производительности, а также тщательно протестированы на отсутствие ошибок. Используя эти контейнеры, можно исключить потенциальные ошибки и неэффективные приемы в пользовательских структурах данных. Вместо необработанных массивов используйте vector
в качестве последовательного контейнера в C++.
vector<string> apples;
apples.push_back("Granny Smith");
В качестве ассоциативного контейнера по умолчанию используйте map
(не unordered_map
). Используйте set
, multimap
и multiset
для вырожденных и множественных операторов выбора.
map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";
При необходимости оптимизации производительности рассмотрите возможность использования следующих средств.
- Неупорядоченные ассоциативные контейнеры, такие как
unordered_map
. Они имеют меньше издержек на элемент и постоянный по времени поиск, но их сложно использовать правильно и эффективно. - Сортированные
vector
. Дополнительные сведения см. в разделе Алгоритмы.
Не используйте массивы стилей C. Для более старых API, которым требуется прямой доступ к данным, используйте такие методы доступа, как f(vec.data(), vec.size());
. Дополнительные сведения о контейнерах см. в разделе Контейнеры стандартной библиотеки C++.
Алгоритмы стандартной библиотеки
Перед принятием решения о том, что вам нужно написать собственный алгоритм для программы, сначала ознакомьтесь с алгоритмами стандартной библиотеки C++. Стандартная библиотека содержит постоянно увеличивающийся набор различных алгоритмов для многих распространенных операций, таких как поиск, сортировка, фильтрация и рандомизация. Имеется обширная математическая библиотека. В C++17 и более поздних версиях предоставляются параллельные версии многих алгоритмов.
Ниже приведены некоторые важные примеры.
for_each
, алгоритм обхода по умолчанию (наряду с цикламиfor
на основе диапазона).transform
, для изменения элементов контейнера "не на месте".find_if
, алгоритм поиска по умолчанию.sort
,lower_bound
и другие алгоритмы сортировки и поиска по умолчанию.
При написании операторов сравнения по возможности используйте строгие выражения <
и именованные лямбда-выражения.
auto comp = [](const widget& w1, const widget& w2)
{ return w1.weight() < w2.weight(); }
sort( v.begin(), v.end(), comp );
auto i = lower_bound( v.begin(), v.end(), widget{0}, comp );
auto
вместо явных имен типов
В C++ 11 введено ключевое слово auto
для использования в объявлениях переменных, функций и шаблонов. Ключевое слово auto
предписывает компилятору определить тип объекта, чтобы не указывать его явным образом. auto
особенно полезно, когда выводимый тип является вложенным шаблоном.
map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++
Циклы for
на основе диапазона
Итерации в стиле C для массивов и контейнеров подвержены ошибкам индексирования, а также достаточно рутинные. Чтобы устранить эти ошибки и сделать код более удобочитаемым, используйте с контейнерами стандартной библиотеки и необработанными массивами циклы for
на основе диапазона. Дополнительные сведения см. в разделе Оператор for
на основе диапазона.
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v {1,2,3};
// C-style
for(int i = 0; i < v.size(); ++i)
{
std::cout << v[i];
}
// Modern C++:
for(auto& num : v)
{
std::cout << num;
}
}
Выражения constexpr
вместо макросов
Макросы в языках C и C++ являются токенами, которые обрабатываются препроцессором перед компиляцией. Перед компиляцией файла каждый экземпляр токена макроса заменяется определенным значением или выражением. Макросы обычно используются в программировании в стиле C для определения значений констант времени компиляции. Однако макросы подвержены ошибкам и их сложно отлаживать. В современном C++ следует отдавать предпочтение переменным constexpr
для констант времени компиляции.
#define SIZE 10 // C-style
constexpr int size = 10; // modern C++
Унифицированная инициализация
В современном C++ можно использовать инициализацию с помощью фигурных скобок для любого типа. Такая форма инициализации особенно удобна при инициализации массивов, векторов и других контейнеров. В следующем примере v2
инициализируется с тремя экземплярами S
. v3
инициализируется с тремя экземплярами S
, которые сами по себе инициализируются с помощью фигурных скобок. Компилятор выводит тип каждого элемента на основе объявленного типа v3
.
#include <vector>
struct S
{
std::string name;
float num;
S(std::string s, float f) : name(s), num(f) {}
};
int main()
{
// C-style initialization
std::vector<S> v;
S s1("Norah", 2.7);
S s2("Frank", 3.5);
S s3("Jeri", 85.9);
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
// Modern C++:
std::vector<S> v2 {s1, s2, s3};
// or...
std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };
}
Дополнительные сведения см. в разделе Инициализация фигурными скобками.
Семантика перемещения
Современный C++ предоставляет семантику перемещения, что позволяет устранять ненужное копирование памяти. В предыдущих версиях языка в определенных ситуациях копирования нельзя было избежать. Операция перемещения передает владение ресурсом от одного объекта к другому без создания копии. Некоторые классы владеют такими ресурсами, как память кучи, дескрипторы файлов и т. д. При реализации класса, владеющего ресурсами, можно определить для него конструктор перемещения и оператор присваивания перемещения. Компилятор выбирает эти специальные члены класса при разрешении перегрузки в ситуациях, когда копирование не требуется. Типы контейнеров стандартной библиотеки вызывают для объектов конструктор перемещения, если он определен. Дополнительные сведения см. в разделе Конструкторы перемещения и операторы присваивания перемещения (C++).
Лямбда-выражения
В программировании в стиле C функцию можно передать в другую функцию с помощью указателя функции. Указатели функций неудобно поддерживать и сложно понимать. Функция, на которую они ссылаются, может быть определена в любом месте исходного кода, далеко от точки ее вызова. Кроме того, они не являются типобезопасными. Современный C++ предоставляет объекты-функции — классы, переопределяющие оператор operator()
, который позволяет вызывать их как функцию. Наиболее удобный способ создания объектов-функций — встроенные лямбда-выражения. В следующем примере показано, как использовать лямбда-выражение для передачи объекта-функции, которую функция find_if
будет вызывать для каждого элемента в векторе.
std::vector<int> v {1,2,3,4,5};
int x = 2;
int y = 4;
auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });
Лямбда-выражение можно считать как "функция, которая принимает один аргумент типа int
и возвращает логическое значение[=](int i) { return i > x && i < y; }
, указывающее, больше x
ли аргумент и меньшеy
". Обратите внимание, что переменные x
и y
из окружающего контекста можно использовать в лямбда-лямбда-файле. [=]
указывает, что эти переменные записываются по значению, то есть лямбда-выражение имеет собственные копии этих значений.
Исключения
Современный C++ подчеркивает исключения, а не коды ошибок, как лучший способ сообщать и обрабатывать условия ошибок. Дополнительные сведения см. в разделе Современный подход к обработке исключений и ошибок в C++.
std::atomic
Используйте структуру и связанные типы std::atomic
стандартной библиотеки C++ для механизмов взаимодействия между потоками.
std::variant
(C++17)
Объединения обычно используются в программировании в стиле C для экономии памяти, позволяя членам разных типов занимать одно и то же расположение в памяти. Однако объединения не являются типобезопасными и могут быть подвержены ошибкам программирования. В C++ 17 появился класс std::variant
в качестве более надежной и безопасной альтернативы объединениям. Функция std::visit
может использоваться для доступа к членам типа variant
типобезопасным способом.
См. также
Справочник по языку C++
Лямбда-выражения
Стандартная библиотека C++
Соответствие стандартам языка Microsoft C/C++
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по