Форум » C/C++ » Изменение вида кода программы при переходе с С++ 2003 на С++ 2011 » Ответить

Изменение вида кода программы при переходе с С++ 2003 на С++ 2011

Сыроежка: Интересно сравнить, насколько сильно будет различаться код даже простого примера при переходе с С++ 2003 на С++ 2011. В качестве такого примера можно рассмотреть часто встречающееся задание для студентов: в заданном массиве поменять местами максимальный и минимальный элементы. Для простоты значения массива зададим при его определении. Затем их перемешаем с помощью стандартного алгоритма std::random_shuffle и произведем перестановку местами максимального и минимального элементов. Будем выводить на консоль значения массива до перестановки и после перестановки с помощью стандартного алгоритма std::copy. Вот как будет выглядеть соответсвующипй код на С++ 2003 [pre2]#include <iostream> #include <algorithm> #include <iterator> int main() { int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; const std::size_t SIZE = sizeof( a ) / sizeof( *a ); std::random_shuffle( a, a + SIZE ); std::copy( a, a + SIZE, std::ostream_iterator<int>( std::cout, " " ) ); std::cout << std::endl; std::iter_swap( std::max_element( a, a + SIZE ), std::min_element( a, a + SIZE ) ); std::copy( a, a + SIZE, std::ostream_iterator<int>( std::cout, " " ) ); std::cout << std::endl; }[/pre2] Проверить работу этого кода можно, например, с помощью он-лайнового компилятора. Просто скопируйте в окно редактора этого компилятора текст примера, отформатируйте его (так как почему-то символ перевода строки не включается в копию текста) и запустите на выполнение. Этот же пример можно выполнить с помощью MS VC++ 2010. Теперь интересно посмотреть, как этот код будет выглядеть, будучи написанным с использованием синтаксических конструкциий С++ 2011. [pre2]#include <iostream> #include <algorithm> #include <iterator> int main() { int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; std::random_shuffle( std::begin( a ), std::end( a ) ); for ( const auto &i : a ) std::cout << i << " "; std::cout << std::endl; auto p = std::minmax_element( std::begin( a ), std::end( a ) ); std::iter_swap( p.first, p.second ); for ( const auto &i : a ) std::cout << i << " "; std::cout << std::endl; }[/pre2] Этот пример также моожно запустить на выполнение с помощью он-лайнового компилятора. Но уже на MS VC++ 2011 этот пример не будет компилироваться, так как этот компилятор не поддерживает пока еще такие синтаксические конструкции, как циклы, основанные на диапазоне, то есть конструкциии вида for ( const auto &i : a ) Чтобы код компилировался с помощью MS VC++ 2010, придется снова вернуться к алгоритму std::copy для вывода значений элементов массива на консоль вместо использования цикла for, основанного на диапазоне. Если сравнить приведенные примеры кода, то создается впечатление, что они написаны на разных языках программирования! В примерах используются различные синтаксические конструкции. Так при использовании С++ 2011 вместо вычисления константы, равной размеру массива, применяемой для задания диапазона массива, используются глобальные функции std::begin и std::end. Вместо алгоритма std::copy и выходного итератора потока std::oostream_iterator<int> для вывода на консоль значений элементов массива используется цикл, основаннный на диапазоне for ( const auto &i : a ). Вместо вызова двух алгоритмов std::max_element и std::min_element, что приводило к двойному обходу одного и того же массива, чтобы отдельно найти максимальный и минимальный элементы, в С++ 2011 используется новый алгоритм, который за один обход массива вычисляет максимальный и минимальный элементы. В результате всех этих изменений второй пример кардинально отличается от первого примера до такой степени, что создается впечатление, что использовались хотя и близкие по синтаксису, но различные языки программирования!

Ответов - 2 новых

Сыроежка: В качестве "десерта" к предыдущему примеру можете рассмотреть следующую экзотическую конструкцию, которая имеет право на существование благодаря новому стандарту С++ 2011. Код представлен в виде, предназначенным для запуска в MS VC++ 2010. Если требуется скомпилировать его с помощью другого компилятора (естественно такого, который поддерживает средства стандарта С++ 2011), то следует убрать из кода строку с #include "stdafx.h", а объявление функции _tmain упростить до int main() [pre2] #include "stdafx.h" #include <iostream> template <typename T1, typename T2> auto add( T1 a, T2 b ) -> decltype( a + b ) { return ( a + b ); } int _tmain(int argc, _TCHAR* argv[]) { unsigned x = 1; long y = 2; auto z = add( x, y ); std::cout << "z = " << z << std::endl; }[/pre2] Заодно и проверьте свои знания С/С++, ответив на вопрос (без запуска примера), чему будет равен тип переменной z?

Сыроежка: Первый, рассмотренный в этой теме пример был бы не полным без одного уточнения. В этом примере значения массива задавались при его инициализации в списке инициализации: [pre2] int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };[/pre2] Это упрощение не ограничивает общности подхода к сравнению вида кода в С++ 2003 и в С++ 2011. Но тем не менее и здесь, то есть по отношению к этой строке инициализации массива можно продемонстрировать различия С++ 2003 и С++ 2011. Это различие проявит себя, если задаться вопросом, а как можно задать последовательные значения массива равными 1, 2,..., n не в списке инициализации, а алгоритмическим способои? Ведь не всегда массивы имеют столь мало элементов, что можно их инициализировать "вручную". Опять-таки для простоты примем размер массива таким же, как и в исходном примере. [pre2]const int SIZE = 9; [/pre2] Но теперь поставим задачу инициализировать массив алгоритмически. Самый простой путь в С++ 2003 - это по-прежнему использовать цикл. [pre2]for ( int i = 1; i < SIZE; i++ ) a[ i ] = i + 1;[/pre2] А как это сделать с помощью стандартных аллгоритмов в С++ 2003? Увы, простого пути нет. Возможное решение задачи следующее: [pre2]#include <iostream> #include <iterator> #include <algorithm> #include <numeric> int main() { const int SIZE = 9; int a[SIZE]; std::fill( a, a + SIZE, 1 ); std::partial_sum( a, a + SIZE, a ); std::copy( a, a + SIZE, std::ostream_iterator<int>( std::cout, " " ) ); std::cout << sttd::endl; }[/pre2] В этом примере сначала с помощью стандартного алгоритма std::fill, объявленного в <algorithm>, всем элементам массива присваивается одно и то же значений, равное 1. А затем используется стандартный алгоритм std::partial_sum, объявленный в заголовочном файле <numeric>, который подсчитывает частичные суммы исходного массива и присваивает их элементам этого же массива. То есть a[0] так и останется равным 1. a[1] будет равно 1 + a[1], то есть 2. a[2] будет равно 2 + a[2], то есть 3 и т.д. Конечно для контейнеров с прямым доступом к элементам, как, например, для массивов или векторов, проще использовать цикл. Для последовательных контейнеров типа std::list этот цикл уже не будет таким простым, так как надо организовать еще перемещение по элементам коонтейнера. Поэтому для контейнеа std::list лучше организовать цикл с помощью итераторов, например, [pre2]#include <iostream> #include <iterator> #include <algorithm> #include <list> int main() { const int SIZE = 9; std::list<int> l( SIZE, 1 ); std::list<int>::iterator it = l.begin(); int sum = *it; while ( ++it != l.end() ) { sum += *it; *it = sum; } std::copy( l.begin(), l.end(), std::ostream_iterator<int>( std::cout, " " ) ); std::cout << sttd::endl; }[/pre2] Как видно из примера, это тоже не простой путь, а самое главное, требует от читающего код затратить определенное время и умственные усилия, чтобы разобраться, что этот код делает. Хуже еще то, что в одной и той же программе для разных типов контейнеров могут использоваться различные подходы для решения данной задачи, то есть для массивов будет использоваться цикл с прямым доступом к элементам, для списков - другой цикл с итераторами. а для векторов, допустим, - третий с использованием стандартных алгоритмов std::fill и std::partiall_sum, как было показано выше. Что же предлагает новый стандарт С++ 2011 для решения подобной задачи? В стандарте С++ 2011 в библиотеки <numeric> появился новый алгоритм (если я не ошибаюсь, это единственное изменение библиотеки <numeric> в станларте С++ 2011) std::iota. Он как раз и предназначен для решения подобной задачии. Теперь с его появлением можно для всех ттипов контейнеров использовать один и тот же метод. Например, [pre2]#include <iostream> #include <iterator> #include <algorithm> #include <numeric> #include <list> int main() { const int SIZE = 9; int a[SIZE]; std::iota( std::begin( a ), std::end( a ), 1 ); std::copy( std::begin( a ), std::end( a ), std::ostream_iterator<int>( std::cout, " " ) ); std::cout << std::endl; std::list<int> l( SIZE ); std::iota( l.begin(), l.end(), 1 ); std::copy( l.begin(), l.end(), std::ostream_iterator<int>( std::cout, " " ) ); std::cout << sttd::endl; }[/pre2] Главное достоинство этого подхода состоит в том, что стандартные алгоритмы - это тот язык общения между программистами, который понятен любому квалифицированному программисту, в отличии от оригинальных конструкций, придумываемых каждым программистом самостоятельно в своем коде.



полная версия страницы