В недавнем посте хабровчане проголосовали за то, чтобы главы из книги были переведены на русский. Немного подумав я решил схитрить, и вместо перевода глав из открытого доступа, рассказать о том, что есть в закрытых.
Дабы не нарушать прав издательства, это будет не дословный перевод, а выжимка из тех техник, что могут показаться интересными даже людям не работающим с 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, рассказывать можно долго. Что бы вы хотели услышать в первую очередь:
ссылка на оригинал статьи http://habrahabr.ru/post/206412/
Добавить комментарий