Скотт Мейерс - Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14
- Название:Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:2016
- Город:Москва
- ISBN:978-5-8459-2000-3
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Мейерс - Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 краткое содержание
В книге рассматриваются следующие темы. Освоение С++11 и С++14 — это больше, чем просто ознакомление с вводимыми этими стандартами возможностями (например, объявлениями типов
, семантикой перемещения, лямбда-выражениями или поддержкой многопоточности). Вопрос в том, как использовать их эффективно, чтобы создаваемые программы были корректны, эффективны и переносимы, а также чтобы их легко можно было сопровождать. Именно этим вопросам и посвящена данная книга, описывающая создание по-настоящему хорошего программного обеспечения с использованием C++11 и С++14 — т.е. с использованием современного С++.
■ Преимущества и недостатки инициализации с помощью фигурных скобок, спецификации
, прямой передачи и функций
интеллектуальных указателей
■ Связь между
,
, rvalue-ссылками и универсальными ссылками
■ Методы написания понятных, корректных,
лямбда-выражений
■ Чем
отличается от
, как они используются и как соотносятся с API параллельных вычислений С++
■ Какие из лучших методов “старого” программирования на С++ (т.е. С++98) должны быть пересмотрены при работе с современным С++
Более чем 20 лет книги
серии
являются критерием уровня книг по программированию на С++. Понятное пояснение сложного технического материала принесло ему всемирную известность. Он всегда самый желанный гость на международных конференциях, а его услуги консультанта широко востребованы во всем мире.
Скотт Мейерс Эффективный и современный С++, После изучения основ С++ я перешел к изучению того, как применять С++ в промышленном программировании, с помощью серии книг Скотта Мейерса Эффективный С++. Эффективный и современный С++ — наиболее важная из книг серии, предлагающая ключевые рекомендации, стили и идиомы, позволяющие эффективно использовать современный С++. Вы еще не купили эту книгу? Сделайте это прямо сейчас. Герб Саттер,
глава Комитета ISO по стандартизации С++, специалист в области архитектуры программного обеспечения на С++ в Microsoft
Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
class Warning { // Потенциально старый класс из С++98
public:
…
void override(); // Корректно как в С++98, так и в C++11
// (с тем же смыслом)
};
Это все, что следует сказать об override
, но это не все, что следует сказать о ссылочных квалификаторах функций-членов. Я обещал, что поговорю о них позже, и вот сейчас как раз и настало это “позже”.
Если мы хотим написать функцию, которая принимает только аргументы, являющиеся lvalue, мы объявляем параметр, который представляет собой неконстантную lvalue-ссылку:
void doSomething(Widget& w); // Принимает только lvalue Widget
Если же мы хотим написать функцию, которая принимает только аргументы, являющиеся rvalue, мы объявляем параметр, который представляет собой rvalue-ссылку:
void doSomething(Widget&& w); // Принимает только rvalue Widget
Ссылочные квалификаторы функции-члена позволяют проводить такое же различие для объектов, функции-члены которых вызываются, т.e. *this
. Это точный аналог модификатора const
в конце объявления функции-члена, который указывает, что объект, для которого вызывается данная функция-член (т.e. *this
), является const
.
Необходимость в функциях-членах со ссылочными квалификаторами нужна не так уж часто, но может и возникнуть. Предположим, например, что наш класс Widget
имеет член-данные std::vector
, и мы предлагаем функцию доступа, которая обеспечивает клиентам к нему непосредственный доступ:
class Widget {
public:
using DataType = std::vector; // См. информацию о
// using в разделе 3.3
DataType& data() { return values; }
…
private:
DataType values;
};
Вряд ли это наиболее инкапсулированный дизайн, который видел свет, но оставим этот вопрос в стороне и рассмотрим, что происходит в следующем клиентском коде:
Widget w;
…
auto vals1 = w.data(); // Копирует w.values в vals1
Возвращаемый тип Widget::data
представляет собой lvalue-ссылку (чтобы быть точным — std::vector&
), а поскольку lvalue-ссылки представляют собой lvalue, мы инициализируем vals1
из lvalue. Таким образом, vals1
создается копирующим конструктором из w.values
, как и утверждает комментарий.
Теперь предположим, что у нас имеется фабричная функция, которая создает Widget
:
Widget makeWidget();
и мы хотим инициализировать переменную с помощью std::vector
в Widget
, возвращенном из makeWidget
:
auto vals2 = makeWidget().data(); // Копирование значений в
// Widget в vals2
И вновь Widgets::data
возвращает lvalue-ссылку, и вновь lvalue-ссылка представляет собой lvalue, так что наш новый объект ( vals2
) опять является копией, построенной из values
в объекте Widget
. Однако в этот раз Widget
представляет собой временный объект, возвращенный из makeWidget
(т.e. представляет собой rvalue), так что копирование в него std::vector
представляет собой напрасную трату времени. Предпочтительнее выполнить перемещение, но, поскольку data
возвращается как lvalue-ссылка, правила С++ требуют, чтобы компиляторы генерировали код для копирования. (Имеется некоторый маневр для оптимизации на основе правила “как если бы” [5] “As if rule” — правило, согласно которому разрешены любые преобразования кода, не изменяющие наблюдаемое поведение программы. — Примеч. ред .
, но было бы глупо полагаться та то, что ваш компилятор найдет способ им воспользоваться.)
Нам необходим способ указать, что, когда data
вызывается для Widget
, являющегося rvalue, результат также будет представлять собой rvalue. Использование ссылочных квалификаторов для перегрузки data
для Widget
, являющихся lvalue и rvalue, делает это возможным:
class Widget {
public:
using DataType = std::vector;
DataType& data() & // Для lvalue Widget,
{ return values; } // возвращает lvalue
DataType&& data() && // Для rvalue Widget,
{ return std::move(values); } // возвращает rvalue
…
private:
DataType values;
};
Обратите внимание на разные возвращаемые типы перегрузок data
. Перегрузка для lvalue-ссылки возвращает lvalue-ссылку (т.e. lvalue), а перегрузка для rvalue-ссылки возвращает rvalue-ссылку (которая, как возвращаемый тип функции, является rvalue). Это означает, что клиентский код ведет теперь себя так, как мы и хотели:
auto vals1 = w.data(); // Вызывает lvalue-перегрузку
// Widget::data, vals1
// создается копированием
auto vals2 = makeWidget().data(); // Вызывает rvalue-перегрузку
// Widget::data, vals2
// создается перемещением
Это, конечно, хорошо, но не позвольте теплому сиянию этого хэппи-энда отвлечь вас от истинной цели этого раздела. Эта цель в том, чтобы убедить вас, что всякий раз, когда вы объявляете в производном классе функцию, предназначенную для перекрытия виртуальной функции базового класса, вы не забывали делать это с использованием ключевого слова override
.
Кстати, если функция-член использует ссылочный квалификатор, все перегрузки этой функции также должны использовать его. Это связано с тем, что перегрузки без этих квалификаторов могут вызываться как для объектов lvalue, так и для объектов rvalue. Такие перегрузки будут конкурировать с перегрузками, имеющими ссылочные квалификаторы, так что все вызовы функции будут неоднозначными.
• Объявляйте перекрывающие функции как override
.
• Ссылочные квалификаторы функции-члена позволяют по-разному рассматривать lvalue- и rvalue-объекты ( *this
).
3.7. Предпочитайте итераторы const_iterator
итераторам iterator
Итераторы const_iterator
представляют собой STL-эквивалент указателя на const
. Они указывают на значения, которые не могут быть изменены. Стандартная практика применения const
там, где это только возможно, требует применения const_iterator
везде, где нужен итератор, но не требуется изменять то, на что этот итератор указывает.
Это верно как для С++98, так и для C++11, но в С++98 поддержка const_iterator
носит половинчатый характер. Такие итераторы не так легко создавать, а если у вас уже имеется такой итератор, его использование весьма ограничено. Предположим, например, что вы хотите выполнить в std::vector
поиск первого встречающегося значения 1983 (год, когда название языка программирования “С с классами” сменилось на С++), а затем вставить в это место значение 1998 (год принятия первого ISO-стандарта С++). Если в векторе нет значения 1983, вставка выполняется в конец вектора. При использовании итераторов iterator в С++98 сделать описанное просто:
std::vector values;
std::vector::iterator it =
std::find(values.begin(), values.end(), 1983);
values.insert(it, 1998);
Но iterator
— в данном случае не совсем верный выбор, поскольку этот код не изменяет объект, на который указывает iterator
. Переделка кода для использования const_iterator
должна быть тривиальной задачей… но не в С++98. Вот один из подходов, концептуально надежный, но все еще не совсем корректный:
Интервал:
Закладка: