On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 28.07.12 18:16. Заголовок: Изменение вида кода программы при переходе с С++ 2003 на С++ 2011


Интересно сравнить, насколько сильно будет различаться код даже простого примера при переходе с С++ 2003 на С++ 2011. В качестве такого примера можно рассмотреть часто встречающееся задание для студентов: в заданном массиве поменять местами максимальный и минимальный элементы.
Для простоты значения массива зададим при его определении. Затем их перемешаем с помощью стандартного алгоритма std::random_shuffle и произведем перестановку местами максимального и минимального элементов. Будем выводить на консоль значения массива до перестановки и после перестановки с помощью стандартного алгоритма std::copy. Вот как будет выглядеть соответсвующипй код на С++ 2003

#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;
}


Проверить работу этого кода можно, например, с помощью он-лайнового компилятора. Просто скопируйте в окно редактора этого компилятора текст примера, отформатируйте его (так как почему-то символ перевода строки не включается в копию текста) и запустите на выполнение.

Этот же пример можно выполнить с помощью MS VC++ 2010.

Теперь интересно посмотреть, как этот код будет выглядеть, будучи написанным с использованием синтаксических конструкциий С++ 2011.

#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;
}


Этот пример также моожно запустить на выполнение с помощью он-лайнового компилятора.

Но уже на 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 используется новый алгоритм, который за один обход массива вычисляет максимальный и минимальный элементы.
В результате всех этих изменений второй пример кардинально отличается от первого примера до такой степени, что создается впечатление, что использовались хотя и близкие по синтаксису, но различные языки программирования!


Спасибо: 0 
ПрофильЦитата Ответить
Ответов - 2 [только новые]





ссылка на сообщение  Отправлено: 28.07.12 18:17. Заголовок: В качестве "десе..


В качестве "десерта" к предыдущему примеру можете рассмотреть следующую экзотическую конструкцию, которая имеет право на существование благодаря новому стандарту С++ 2011. Код представлен в виде, предназначенным для запуска в MS VC++ 2010. Если требуется скомпилировать его с помощью другого компилятора (естественно такого, который поддерживает средства стандарта С++ 2011), то следует убрать из кода строку с #include "stdafx.h", а объявление функции _tmain упростить до int main()

 #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;
}


Заодно и проверьте свои знания С/С++, ответив на вопрос (без запуска примера), чему будет равен тип переменной z?



Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 28.07.12 18:18. Заголовок: Первый, рассмотренны..


Первый, рассмотренный в этой теме пример был бы не полным без одного уточнения. В этом примере значения массива задавались при его инициализации в списке инициализации:

   int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };


Это упрощение не ограничивает общности подхода к сравнению вида кода в С++ 2003 и в С++ 2011. Но тем не менее и здесь, то есть по отношению к этой строке инициализации массива можно продемонстрировать различия С++ 2003 и С++ 2011.
Это различие проявит себя, если задаться вопросом, а как можно задать последовательные значения массива равными 1, 2,..., n не в списке инициализации, а алгоритмическим способои? Ведь не всегда массивы имеют столь мало элементов, что можно их инициализировать "вручную".

Опять-таки для простоты примем размер массива таким же, как и в исходном примере.

const int SIZE = 9; 


Но теперь поставим задачу инициализировать массив алгоритмически. Самый простой путь в С++ 2003 - это по-прежнему использовать цикл.

for ( int i = 1; i < SIZE; i++ ) a[ i ] = i + 1;


А как это сделать с помощью стандартных аллгоритмов в С++ 2003? Увы, простого пути нет. Возможное решение задачи следующее:

#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;
}


В этом примере сначала с помощью стандартного алгоритма 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 лучше организовать цикл с помощью итераторов, например,

#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;
}


Как видно из примера, это тоже не простой путь, а самое главное, требует от читающего код затратить определенное время и умственные усилия, чтобы разобраться, что этот код делает. Хуже еще то, что в одной и той же программе для разных типов контейнеров могут использоваться различные подходы для решения данной задачи, то есть для массивов будет использоваться цикл с прямым доступом к элементам, для списков - другой цикл с итераторами. а для векторов, допустим, - третий с использованием стандартных алгоритмов std::fill и std::partiall_sum, как было показано выше.

Что же предлагает новый стандарт С++ 2011 для решения подобной задачи?
В стандарте С++ 2011 в библиотеки <numeric> появился новый алгоритм (если я не ошибаюсь, это единственное изменение библиотеки <numeric> в станларте С++ 2011) std::iota. Он как раз и предназначен для решения подобной задачии. Теперь с его появлением можно для всех ттипов контейнеров использовать один и тот же метод. Например,


#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;
}


Главное достоинство этого подхода состоит в том, что стандартные алгоритмы - это тот язык общения между программистами, который понятен любому квалифицированному программисту, в отличии от оригинальных конструкций, придумываемых каждым программистом самостоятельно в своем коде.


Спасибо: 0 
ПрофильЦитата Ответить
Ответ:
1 2 3 4 5 6 7 8 9
большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

показывать это сообщение только модераторам
не делать ссылки активными
Имя, пароль:      зарегистрироваться    
Тему читают:
- участник сейчас на форуме
- участник вне форума
Все даты в формате GMT  3 час. Хитов сегодня: 42
Права: смайлы да, картинки да, шрифты да, голосования нет
аватары да, автозамена ссылок вкл, премодерация откл, правка нет