В недавнем постее проголосовали за то, чтобы главы из книги были переведены на русский. Немного подумав я решил схитрить, и вместо перевода глав из открытого доступа, рассказать о том, что есть в закрытых.
Дабы не нарушать прав издательства, это будет не дословный перевод, а выжимка из тех техник, что могут показаться интересными даже людям не работающим с Boost.
Что вас ждёт под катом:
- Избегаем вызовов макросов вместо функций, на примере max/min.
- Вызываем оптимальную функцию, на примере std::swap и её специализации в разных пространствах имен.
- Ускоряем вставку в std::vector.
- Деструкторы в C++11.
Избегаем вызовов макросов вместо функций, на примере max/min.
Те кто много работают с Visual Studio наверняка сталкивались с тем, что min/max — это макросы. Из-за чего практически любые функции min/max в любых классах и пространствах имен трактуются препроцессором как макроподстановки. В следствие этого следующие примеры кода не компилируются:
int max_int = std::numeric_limits<int>::max();
my_class_variable.max();
Есть различные способы избежать вызовов макросов вместо функций, но самый переносимый и короткий — это просто заключить весь вызов функции в круглые скобки (кроме самих круглых скобок):
int max_int = (std::numeric_limits<int>::max)();
(my_class_variable.max)();
Теперь препроцессор не воспримет max как вызов макроса и код скомпилируется верно. Этот трюк срабатывает со всеми макросами, не только с min/max, и часто используется в Boost.
Вызываем оптимальную функцию, на примере std::swap и её специализации в разных пространствах имен.
Представьте себе ситуацию: вам нужно обменять значения двух переменных наиболее эффективным образом. Обычно для этого используют функции swap. Но вот беда, мы пишем обобщенный код который должен работать с пользовательскими типами:
template <class T>
void my_function(T& value1, T& value1) {
// ...
std::swap(value1, value2); // не оптимальное решение!
// ...
}
Неоптимальность решения заключается в том, что пользовательские типы могут иметь свою функцию swap, которая работает намного эффективнее стандартной версии:
namespace some_namespace {
class my_vector;
void swap(my_vector& value1, my_vector& value2);
} // some_namespace
Очень простым решением проблемы будет исправить код следующим образом:
template <class T>
void my_function(T& value1, T& value1) {
using std::swap;
// ...
swap(value1, value2);
// ...
}
Теперь компилятор в первую очередь будет пытаться найти функцию swap из пространств имен параметров value1 и value2. Другими словами, если value1 и value2 являются экземплярами класса some_namespace::my_vector, то компилятор в первую очередь будет пытаться использовать swap из some_namespace. Если в пространствах имен параметров value1 и value2 не будет функции swap, компилятор будет использовать std::swap.
Почему происходит именно так? Потому что компилятор должен выполнить Argument Dependent Lookup (он же Koenig Lookup) прежде чем пытаться сделать вызов функции из using. Именно этим трюком пользуется boost::swap, а boost::numeric_cast использует аналогичный подход для функций floor, ceil и др.
Ускоряем вставку в std::vector.
Начнем с достаточно тривиального совета: если вы знаете количество элементов, которые будут вставлены в вектор, то перед вставкой необходимо вызвать reserve(количество элементов для вставки):
std::vector<int> numbers;
numbers.reserve(1000);
for (size_t i = 0; i < 1000; ++i)
numbers.push.back(i);
// ...
Этот совет имеется во множестве учебников по программированию, но почему-то о нем часто забывают. Из недавних примеров где об этом забыли — код Unity (графической оболочки Ubuntu).
Теперь менее тривиальный совет. В C++11 было решено, что std::vector и ряд других контейнеров могут использовать move-конструкторы и move-assignment операторы для элементов только если move-конструкторы и move-assignment операторы этих элементов не кидают исключений (ну или если элементы не могут копироваться).
Другими словами, в C++11 для достижения наилучшей производительности стоит помечать конструкторы и операторы, не кидающие исключения, как noexcept.
Помимо контейнеров из стандартной библиотеки, многие классы из Boost чувствительны к noexcept, например boost::variant и boost::circular_buffer.
Деструкторы в C++11.
Ещё до стандарта С++11 кидать исключения в деструкторах считалось очень плохим тоном. В C++11 это ведет к смерти приложения.
В С++11 все деструкторы объектов автоматически помечаются как noexcept. Это ведет к тому, что если у вас есть класс, который кидает исключение в деструкторе, то в C++11 в этом случае будет вызван std::terminate() и всё приложение завершится без вызова деструкторов для созданных объектов.
Конечно можно это обойти, явно сказать компилятору что деструктор кидает исключение… Но не лучше ли сделать все по хорошему?
Вместо итогов
В книге имеется много интересного по С++ и Boost, рассказывать можно долго. Что бы вы хотели услышать в первую очередь:
Автор: antoshkka