Возвращение к C++ — современный C++Welcome back to C++ - Modern C++

С момента своего создания C++ стал одним из наиболее широко используемых языков программирования в мире.Since its creation, C++ has become one of the most widely used programming languages in the world. Грамотно сконструированные программы на языках C++ быстры и эффективны.Well-written C++ programs are fast and efficient. Он более гибок, чем другие языки: он может работать с самыми высокими уровнями абстракции, а также на низком аппаратном уровне.The language is more flexible than other languages: It can work at the highest levels of abstraction, and down at the level of the silicon. C++ предоставляет стандартные библиотеки с высоким уровнем оптимизации.C++ supplies highly optimized standard libraries. Он обеспечивает доступ к аппаратным функциям низкого уровня, чтобы максимально увеличить скорость и сократить потребление памяти.It enables access to low-level hardware features, to maximize speed and minimize memory requirements. С помощью C++ можно создавать широкий спектр приложений.Using C++, you can create a wide range of apps. Игры, драйверы устройств и высокопроизводительное научное программное обеспечение.Games, device drivers, and high-performance scientific software. Встраиваемые программы.Embedded programs. Клиентские приложения Windows.Windows client apps. Даже библиотеки и компиляторы для других языков программирования пишутся на C++.Even libraries and compilers for other programming languages get written in C++.

Одно из начальных требований для C++ — обратная совместимость с языком C.One of the original requirements for C++ was backward compatibility with the C language. В результате программы на C++ всегда можно писать в стиле C: с необработанными указателями, массивами, символьными строками с завершающим нулем и другими функциями.As a result, C++ has always permitted C-style programming, with raw pointers, arrays, null-terminated character strings, and other features. Это может обеспечить высокую производительность, но также может приводить к ошибкам и увеличению сложности.They may enable great performance, but can also spawn bugs and complexity. Эволюция C++ концентрируется на возможностях, которые значительно снижают необходимость использования идиом в стиле C.The evolution of C++ has emphasized features that greatly reduce the need to use C-style idioms. Старые средства программирования C все еще можно использовать там, где это необходимо, но в современном C++ они нужны все реже и реже.The old C-programming facilities are there when you need them, but with modern C++ code you should need them less and less. Современный код на C++ проще, безопаснее, элегантнее и так же быстр, как и раньше.Modern C++ code is simpler, safer, more elegant, and still as fast as ever.

В следующих разделах приводятся общие сведения об основных возможностях современного C++.The following sections provide an overview of the main features of modern C++. Если не указано иное, перечисленные здесь функции доступны в C++ 11 и более поздних версиях.Unless noted otherwise, the features listed here are available in C++11 and later. В компиляторе C++ от Майкрософт с помощью параметра /std можно указать версию стандарта, используемую для проекта.In the Microsoft C++ compiler, you can set the /std compiler option to specify which version of the standard to use for your project.

Ресурсы и интеллектуальные указателиResources and smart pointers

Одним из основных классов ошибок в программировании в стиле C является утечка памяти.One of the major classes of bugs in C-style programming is the memory leak. Утечки часто возникают из-за невозможности вызвать delete для памяти, выделенной с помощью new .Leaks are often caused by a failure to call delete for memory that was allocated with new. Современный C++ придерживается принципа получение ресурса есть инициализация (англ. Resource Acquisition Is Initialization (RAII)).Modern C++ emphasizes the principle of resource acquisition is initialization (RAII). Идея проста.The idea is simple. Ресурсы (память кучи, дескрипторы файлов, сокеты и т. д.) должны принадлежать объекту.Resources (heap memory, file handles, sockets, and so on) should be owned by an object. Этот объект создает и получает новый выделенный ресурс в конструкторе и удаляет его в его деструкторе.That object creates, or receives, the newly allocated resource in its constructor, and deletes it in its destructor. Принцип RAII гарантирует, что все ресурсы должным образом возвращаются операционной системе, когда объект-владелец выходит за пределы области.The principle of RAII guarantees that all resources get properly returned to the operating system when the owning object goes out of scope.

Для поддержки простого внедрения принципов RAII стандартная библиотека языка C++ предоставляет три типа интеллектуальных указателей: std::unique_ptr, std::shared_ptr и std::weak_ptr.To support easy adoption of RAII principles, the C++ Standard Library provides three smart pointer types: std::unique_ptr, std::shared_ptr, and std::weak_ptr. Интеллектуальный указатель обрабатывает выделение и удаление памяти, которой он владеет.A smart pointer handles the allocation and deletion of the memory it owns. В следующем примере показан класс с членом-массивом, который выделяется в куче в вызове make_unique().The following example shows a class with an array member that is allocated on the heap in the call to make_unique(). Вызовы new и delete инкапсулированы в классе unique_ptr.The calls to new and delete are encapsulated by the unique_ptr class. Когда объект widget выходит из области действия, вызывается деструктор unique_ptr и освобождается память, выделенная для массива.When a widget object goes out of scope, the unique_ptr destructor will be invoked and it will release the memory that was allocated for the array.

#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

При выделении памяти кучи всегда, когда это возможно, используйте интеллектуальные указатели.Whenever possible, use a smart pointer when allocating heap memory. Если необходимо явно использовать операторы new и delete, следуйте принципу RAII.If you must use the new and delete operators explicitly, follow the principle of RAII. Дополнительные сведения см. в разделе Управление временем жизни и ресурсами объекта (RAII).For more information, see Object lifetime and resource management (RAII).

std::string и std::string_viewstd::string and std::string_view

Строки в стиле C — это еще один основной источник ошибок.C-style strings are another major source of bugs. Используя std::string и std::wstring, можно устранить практически все ошибки, связанные со строками в стиле C.By using std::string and std::wstring, you can eliminate virtually all the errors associated with C-style strings. Дополнительно вы получаете преимущества функций-членов для поиска, добавления в конец и начало и т. д.You also gain the benefit of member functions for searching, appending, prepending, and so on. Оба эти класса оптимизированы для быстрой работы.Both are highly optimized for speed. При передаче строки в функцию, для которой требуется доступ только для чтения, в C++ 17 можно использовать std::string_view для еще большего выигрыша в производительности.When passing a string to a function that requires only read-only access, in C++17 you can use std::string_view for even greater performance benefit.

std::vector и другие контейнеры стандартной библиотекиstd::vector and other Standard Library containers

Все контейнеры стандартной библиотеки следуют принципу RAII.The standard library containers all follow the principle of RAII. Они предоставляют итераторы для безопасного обхода элементов.They provide iterators for safe traversal of elements. И они хорошо оптимизированы для повышения производительности, а также тщательно протестированы на отсутствие ошибок.And, they're highly optimized for performance and have been thoroughly tested for correctness. Используя эти контейнеры, можно исключить потенциальные ошибки и неэффективные приемы в пользовательских структурах данных.By using these containers, you eliminate the potential for bugs or inefficiencies that might be introduced in custom data structures. Вместо необработанных массивов используйте vector в качестве последовательного контейнера в C++.Instead of raw arrays, use vector as a sequential container in C++.

vector<string> apples;
apples.push_back("Granny Smith");

В качестве ассоциативного контейнера по умолчанию используйте map (не unordered_map).Use map (not unordered_map) as the default associative container. Используйте set, multimap и multiset для вырожденных и множественных операторов выбора.Use set, multimap, and multiset for degenerate and multi cases.

map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";

При необходимости оптимизации производительности рассмотрите возможность использования следующих средств.When performance optimization is needed, consider using:

  • Тип array важен при внедрении, например, как член класса.The array type when embedding is important, for example, as a class member.

  • Неупорядоченные ассоциативные контейнеры, такие как unordered_map.Unordered associative containers such as unordered_map. Они имеют меньше издержек на элемент и постоянный по времени поиск, но их сложно использовать правильно и эффективно.These have lower per-element overhead and constant-time lookup, but they can be harder to use correctly and efficiently.

  • Сортированные vector.Sorted vector. Дополнительные сведения см. в разделе Алгоритмы.For more information, see Algorithms.

Не используйте массивы в стиле языка C.Don’t use C-style arrays. Для более старых API, которым требуется прямой доступ к данным, используйте такие методы доступа, как f(vec.data(), vec.size());.For older APIs that need direct access to the data, use accessor methods such as f(vec.data(), vec.size()); instead. Дополнительные сведения о контейнерах см. в разделе Контейнеры стандартной библиотеки C++.For more information about containers, see C++ Standard Library Containers.

Алгоритмы стандартной библиотекиStandard Library algorithms

Перед принятием решения о том, что вам нужно написать собственный алгоритм для программы, сначала ознакомьтесь с алгоритмами стандартной библиотеки C++.Before you assume that you need to write a custom algorithm for your program, first review the C++ Standard Library algorithms. Стандартная библиотека содержит постоянно увеличивающийся набор различных алгоритмов для многих распространенных операций, таких как поиск, сортировка, фильтрация и рандомизация.The Standard Library contains an ever-growing assortment of algorithms for many common operations such as searching, sorting, filtering, and randomizing. Имеется обширная математическая библиотека.The math library is extensive. Начиная с C++ 17 предоставляются параллельные версии многих алгоритмов.Starting in C++17, parallel versions of many algorithms are provided.

Ниже приведены некоторые важные примеры.Here are some important examples:

  • for_each, алгоритм обхода по умолчанию (наряду с циклами for на основе диапазона).for_each, the default traversal algorithm (along with range-based for loops).

  • transform, для изменения элементов контейнера "не на месте".transform, for not-in-place modification of container elements

  • find_if, алгоритм поиска по умолчанию.find_if, the default search algorithm.

  • sort, lower_bound и другие алгоритмы сортировки и поиска по умолчанию.sort, lower_bound, and the other default sorting and searching algorithms.

При написании операторов сравнения по возможности используйте строгие выражения < и именованные лямбда-выражения.To write a comparator, use strict < and use named lambdas when you can.

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(), comp );

auto вместо явных имен типовauto instead of explicit type names

В C++ 11 введено ключевое слово auto для использования в объявлениях переменных, функций и шаблонов.C++11 introduced the auto keyword for use in variable, function, and template declarations. Ключевое слово auto предписывает компилятору определить тип объекта, чтобы не указывать его явным образом.auto tells the compiler to deduce the type of the object so that you don't have to type it explicitly. auto особенно полезно, когда выводимый тип является вложенным шаблоном.auto is especially useful when the deduced type is a nested template:

map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++

Циклы for на основе диапазонаRange-based for loops

Итерации в стиле C для массивов и контейнеров подвержены ошибкам индексирования, а также достаточно рутинные.C-style iteration over arrays and containers is prone to indexing errors and is also tedious to type. Чтобы устранить эти ошибки и сделать код более удобочитаемым, используйте с контейнерами стандартной библиотеки и необработанными массивами циклы for на основе диапазона.To eliminate these errors, and make your code more readable, use range-based for loops with both Standard Library containers and raw arrays. Дополнительные сведения см. в разделе Оператор for на основе диапазона.For more information, see Range-based for statement.

#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 вместо макросовconstexpr expressions instead of macros

Макросы в языках C и C++ являются токенами, которые обрабатываются препроцессором перед компиляцией.Macros in C and C++ are tokens that are processed by the preprocessor before compilation. Перед компиляцией файла каждый экземпляр токена макроса заменяется определенным значением или выражением.Each instance of a macro token is replaced with its defined value or expression before the file is compiled. Макросы обычно используются в программировании в стиле C для определения значений констант времени компиляции.Macros are commonly used in C-style programming to define compile-time constant values. Однако макросы подвержены ошибкам и их сложно отлаживать.However, macros are error-prone and difficult to debug. В современном C++ следует отдавать предпочтение переменным constexpr для констант времени компиляции.In modern C++, you should prefer constexpr variables for compile-time constants:

#define SIZE 10 // C-style
constexpr int size = 10; // modern C++

Унифицированная инициализацияUniform initialization

В современном C++ можно использовать инициализацию с помощью фигурных скобок для любого типа.In modern C++, you can use brace initialization for any type. Такая форма инициализации особенно удобна при инициализации массивов, векторов и других контейнеров.This form of initialization is especially convenient when initializing arrays, vectors, or other containers. В следующем примере v2 инициализируется с тремя экземплярами S.In the following example, v2 is initialized with three instances of S. v3 инициализируется с тремя экземплярами S, которые сами по себе инициализируются с помощью фигурных скобок.v3 is initialized with three instances of S that are themselves initialized using braces. Компилятор выводит тип каждого элемента на основе объявленного типа v3.The compiler infers the type of each element based on the declared type of 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} };

}

Дополнительные сведения см. в разделе Инициализация фигурными скобками.For more information, see Brace initialization.

Семантика перемещенияMove semantics

Современный C++ предоставляет семантику перемещения, что позволяет устранять ненужное копирование памяти.Modern C++ provides move semantics, which make it possible to eliminate unnecessary memory copies. В предыдущих версиях языка в определенных ситуациях копирования нельзя было избежать.In earlier versions of the language, copies were unavoidable in certain situations. Операция перемещения передает владение ресурсом от одного объекта к другому без создания копии.A move operation transfers ownership of a resource from one object to the next without making a copy. Некоторые классы владеют такими ресурсами, как память кучи, дескрипторы файлов и т. д.Some classes own resources such as heap memory, file handles, and so on. При реализации класса, владеющего ресурсами, можно определить для него конструктор перемещения и оператор присваивания перемещения.When you implement a resource-owning class, you can define a move constructor and move assignment operator for it. Компилятор выбирает эти специальные члены класса при разрешении перегрузки в ситуациях, когда копирование не требуется.The compiler chooses these special members during overload resolution in situations where a copy isn't needed. Типы контейнеров стандартной библиотеки вызывают для объектов конструктор перемещения, если он определен.The Standard Library container types invoke the move constructor on objects if one is defined. Дополнительные сведения см. в разделе Конструкторы перемещения и операторы присваивания перемещения (C++).For more information, see Move Constructors and Move Assignment Operators (C++).

Лямбда-выраженияLambda expressions

В программировании в стиле C функцию можно передать в другую функцию с помощью указателя функции.In C-style programming, a function can be passed to another function by using a function pointer. Указатели функций неудобно поддерживать и сложно понимать.Function pointers are inconvenient to maintain and understand. Функция, на которую они ссылаются, может быть определена в любом месте исходного кода, далеко от точки ее вызова.The function they refer to may be defined elsewhere in the source code, far away from the point at which it's invoked. Кроме того, они не являются типобезопасными.Also, they're not type-safe. Современный C++ предоставляет объекты-функции — классы, переопределяющие оператор operator(), который позволяет вызывать их как функцию.Modern C++ provides function objects, classes that override the operator() operator, which enables them to be called like a function. Наиболее удобный способ создания объектов-функций — встроенные лямбда-выражения.The most convenient way to create function objects is with inline lambda expressions. В следующем примере показано, как использовать лямбда-выражение для передачи объекта-функции, которую функция for_each будет вызывать для каждого элемента в векторе.The following example shows how to use a lambda expression to pass a function object, that the for_each function will invoke on each element in the vector:

    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 i) { return i > x && i < y; } можно прочитать как "функция, которая принимает один аргумент типа int и возвращает логическое значение, указывающее, является ли аргумент больше x и меньше y".The lambda expression [=](int i) { return i > x && i < y; } can be read as "function that takes a single argument of type int and returns a boolean that indicates whether the argument is greater than x and less than y." Обратите внимание, что переменные x и y из окружающего контекста можно использовать в лямбда-выражении.Notice that the variables x and y from the surrounding context can be used in the lambda. [=] указывает, что эти переменные записываются по значению, то есть лямбда-выражение имеет собственные копии этих значений.The [=] specifies that those variables are captured by value; in other words, the lambda expression has its own copies of those values.

ИсключенияExceptions

В современном C++ в качестве способа сообщить об ошибках и обработать их состояние отдается предпочтение исключениям, а не кодам ошибок.Modern C++ emphasizes exceptions rather than error codes as the best way to report and handle error conditions. Дополнительные сведения см. в разделе Современный подход к обработке исключений и ошибок в C++.For more information, see Modern C++ best practices for exceptions and error handling.

std::atomic

Используйте структуру и связанные типы std::atomic стандартной библиотеки C++ для механизмов взаимодействия между потоками.Use the C++ Standard Library std::atomic struct and related types for inter-thread communication mechanisms.

std::variant (C++17)std::variant (C++17)

Объединения обычно используются в программировании в стиле C для экономии памяти, позволяя членам разных типов занимать одно и то же расположение в памяти.Unions are commonly used in C-style programming to conserve memory by enabling members of different types to occupy the same memory location. Однако объединения не являются типобезопасными и могут быть подвержены ошибкам программирования.However, unions aren't type-safe and are prone to programming errors. В C++ 17 появился класс std::variant в качестве более надежной и безопасной альтернативы объединениям.C++17 introduces the std::variant class as a more robust and safe alternative to unions. Функция std::visit может использоваться для доступа к членам типа variant типобезопасным способом.The std::visit function can be used to access the members of a variant type in a type-safe manner.

См. такжеSee also

Справочник по языку C++C++ Language Reference
Лямбда-выраженияLambda Expressions
Стандартная библиотека C++C++ Standard Library
Таблица соответствия Microsoft Visual C++ стандартам языкаMicrosoft C++ language conformance table