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

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



ссылка на сообщение  Отправлено: 28.07.12 18:21. Заголовок: Задание диапазона: std::begin(), std::end()


Новый стандарт языка С++ 2011 изучать тяжело, особенно когда еще не знаешь даже стандарта С++ 2003 в полной мере.
Тем не менее есть вещи в стандарте 2011, которые легко освоить и начать использовать в своем коде уже сегодня, не вникая в тонокости стандарта С++ 2011. Практическая польза от этих нововведений очевидна и не требует каких-либо затрат на их изучение, так как в той или иной мере некоторые программисты уже воплощали эти идеи самостоятельно или по крайней мере ощутили и понимают недостатки того подхода, который они использовали.
Речь пойдет о задании диапазонов итераторов для пооследовательностей. У стандартных контейнеров типа std::vector имеются функции-члены класса, которые позволяют легко задавать диапазон итераторов, охватывающих все элементы контейннера. Это известные функции begin() и end(). Например, чтобы вывести на консоль все элементы вектора с помощью стандартного алгоритма std::copy, можно записать:

#include   <vector> 
#include <iterator>
#include <algorithm>
#include <iostream>

int main()
{
std::vector<int> v( 10, 2 );

std::copy( v.begin(), v.end(), std::ostream_iterator<int>( std::cout, " " ) );
}


В приведенном коде создается вектор из десяти элементов с одинаковыми значениями равными 2, и затем он выводится в стандартный потое std::cout.

Есть два варианта функций-членов класса begin() и end(): один - для неконстантных последовательностей, и другой - для константных последовательностей.

Подобные функции-члены класса объявляются для всех стандартных контейнеров, поэтому код для задания диапазона всех элементов какого-нибудь стандартного контейнера унифицирован. Например, для контейнера std::list вывод его элементов на консоль ничем не отличается от вывода элементов вектора, как в вышеприведенном примере

#include   <list> 
#include <iterator>
#include <algorithm>
#include <iostream>

int main()
{
std::list<int> l( 10, 2 );

std::copy( l.begin(), l.end(), std::ostream_iterator<int>( std::cout, " " ) );
}


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

#include   <iterator> 
#include <algorithm>
#include <iostream>

int main()
{
int a[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };

std::copy( a, a + sizeof( a ) / sizeof( *a ), std::ostream_iterator<int>( std::cout, " " ) );
}


Писать постоянно выражение a + sizeof( a ) / sizeof( *a ) не удобно, к тому же это делает код менее воспринимаемым.
Более продвинутые программисты обычно самостоятельно для массивов пишут шаблонные функции с нзваниеми begin() и end(), которые имитируют собой функции задания диапазонов стандартных коонтейнеров.

template <typename T, size_t N> 
inline T * begin( T ( &a )[ N ] )
{
return ( a );
}

template <typename T, size)t N>
inline T * end( T ( &a )[ N ] )
{
return ( a + N );
}


Тогда предыдущий пример можно переписать следующим образом:

#include   <iterator> 
#include <algorithm>
#include <iostream>

template <typename T, size_t N>
inline T * begin( T ( &a )[ N ] )
{
return ( a );
}

template <typename T, size)t N>
inline T * end( T ( &a )[ N ] )
{
return ( a + N );
}

int main()
{
int a[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };

std::copy( begin( a ), end( a ), std::ostream_iterator<int>( std::cout, " " ) );
}


Этот код легче воспринимать, и он напоминает аналогичный код с использованием стандартных коонтейнеров. Но все же разница существенная. Для контейнеров, например, для контейнера std::vector, используются функции-члены класса, вызов которых выглядет следующим образом: v.begin(), v.end(), то есть с использованием синтаксиса обращения к члену класса. Для массивов же используется другая конструкция: begin( a ), end( a ), то есть используются глобальные функции. Поэтому если в проекте произошла замена массива на вектор, или другой стандартный контейнер, то придется изменять все места в коде, где использовались глобальные функции begin() и end() на соответствующие им функции-члены класса.

К счастью в стандарте С++ 2011 этот вопрос не остался без внимания, и были введены глобальные функции begin() и end() для сттандартных контейнеров. Надо отметить, что еще ранее эти функции были включены в известную библиотеку boost.
Теперь можно пользоваться унифицированным синтаксисом для задания всего диапазона последовательности независимо от того, используется ли стандартный контейнер или массив элементов. Ниже показан пример такого использование, из которого видно, что снтаксис вызова стандартного аллгоритма std::copy не меняется в зависимости от используемой последовательности. Меняется лишь идентификатор этой последовательности, хотя и это соверршенно не обязательно.

#include   <vector> 
#include <iterator>
#include <algorithm>
#include <iostream>

int main()
{
const int a[] = { 1, -3, 0, -6, 7, 0, 5, -9, 8, 0, 0, 3 };

std::copy( std::begin( a ), std::end( a ), std::ostream_iterator<int>( std::cout, " " ) );
std::cout << std::endl;

std::vector<int> v( std::begin( a ), std::end( a ) );

std::copy( std::begin( v ), std::end( v ), std::ostream_iterator<int>( std::cout, " " ) );
std::cout << std::endl;
}


Я думаю, это нововведение стандарта С++ 2011 придется всем по душе, так как оно удобно и не требует никаких новых специальных знаний стандарта С++. Конечно в настоящее время не каждый компилятор скомпилирует этот код, так как не все компиляторы еще поддерживают новый стандарт, но если вы используете MS VC++ 2010, то никаких проблем у вас возникнуть не должно.

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





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


Прочитав предыдущее соообщение о нововведении в стандарте С++ 2011, многие программисты могут прийти к естественному заключению: "Да, появление глобальных функций std::begin() и std::end() очень удобно при использовании их с массивами. Но особой практической пользы от их использования со стандартными контейнерами вместо аналогичных функций-членов классов стандартных контейннеров begin() и end() нет. Разве лишь добавляется некоторый синтаксический "сахар" в виде единообразия задании диапазонов."

Так ли это на самом деле? И действительно ли нет принципиальной практической пользы от применения глобальных функций std::begin() и std::end() со стандартнымаи контейнерами вместо соответствующщих функций-членов классов контейнеров begin() и end()? То есть сужается ли область применения глобальных функций std::begin() и std::end() исключительно до применения их с обычными массивами?

Оказывается, это не так! Есть ситуации, когда без этих глобальных фуннкций просто не обойтись, даже имея дело со стандартными контейнерамии.

Рассмотрим такой пример. Нам нужно присвоить всем элементам двумерного массива некоторое значение. Как это можно сделать? Это можно сделать с использованием стандартного алгоритма std::for_each, который для каждой строки массива в сою очередь будет вызывать стандартный алгоритм std::fill. Вот как это можно сделать с помощью функционального объекта. Для простоты будем заполнять элементы массива нулем.


#include   <algorithm> 

template <typename T, size_t N>
struct fill_array: std::unary_function<T (&)[N], void>
{
void operator ()( T (&a )[N] ) const
{
std::fill( a, a + N, 0 );
}
};

int main()
{
int a[2][3];

std::for_each( a, a + 2, fill_array<int,3>() );
}


Естественно желательно было бы, руководствуясь принципом повторного использования кода, использовать этот функциональный класс и для других типов контейнеров. Но что если мы хотим использовать вектор векторов вместо двумерного массива? Тогда, увы, мы не сможем воспользоваться уже написанным функциональным классом. Придется писать новый функциональный класс. Ниже приведен пример кода для этого сдучая.


#include   <algorithm> 
#include <vector>

template <typename T>
struct fill_container: std::unary_function<T, void>
{
void operator ()( T &c ) const
{
std::fill( c.begin(), c.end(), 0 );
}
};

int main()
{
std::vector<std::vector<int>> v( 2, 3 );

std::for_each( v.begin(), v.end(), fill_container<std::vector<int>>() );
}


Итак, нам пришлось определить два функциональных класса, несмотря на то, что они призваны выполнять одну и ту же задачу. То есть, фактически, произошло дублирование кода для одной и той же логики. Конечно желательно сделать так, чтобы был один функциональный класс для всех типов контейннеров. И вот тут-то не обойтись без глобальных функций std::begin() и std::end(). С помощью этих функций возникшая проблема решается очень просто! Мы можем задать один код для всех видов контейнеров. Вот как это будет выглядеть


#include   <algorithm> 
#include <vector>

template <typename T>
struct fill_2dim: std::unary_function<T, void>
{
void operator ()( T &c ) const
{
std::fill( std::begin( c ), std::end( c ), 0 );
}
};

int main()
{
int a[2][3];

std::for_each( std::begin( a ), std::end( a ), fill_2dim<int[3]>() );

std::vector<std::vector<int>> v( 2, 3 );

std::for_each( std::begin( v ), std::end( v ), fill_2dim<std::vector<int>>() );
}


Именно использование глобальных функций std::begin() и std::end() позволило нам написать единый функциональный класс для контейнеров, объединяющих в себе как стандартные контейнеры, так и массивы.


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



ссылка на сообщение  Отправлено: 28.07.12 18:22. Заголовок: Только к предыдущем..


Только к предыдущему своему сообщению я хотел бы сделать замечание. Помимо многочисленных опечаток (как без опечаток обойтись? ) я во все примеры кода забыл вставить заголовочный файл <functional>
Поэтому если найдется дотошный читатель этой темы, который захочет проверить приведенные примеры, ему следует включить упомянутый мною заголовок <functional>, который содержит определение шаблонного класса std::unary_function, используемый в примерах для создания функциональных классов пользователей.

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



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


Как всегда, в бочке меда, то есть в новом стандарте С++, оказалась ложка дегтя! Принятие полезных глобальных функций для задания диапазонов последовательностей std::begin() и std::end(), похоже, было не до конца продумано, судя по рабочему проекту стандарта С++ 2011. То есть за бортом остались полезные их модификации.
Дело в том, что теперь для контейнеров в стандарт включены новые функции - члены классов контейнеров, которые позволяют явно указать, какой итератор требуется: константный или не константный.
Чтобы было понятно о чем идет речь, то нужно сделать некоторое пояснение. Обычно каждый стандартный контейнер имеет два типа итераторов: iterator и const_iterator , используя iterator можно изменять значение элемента последовательности, на который указывает iterator.

Например.

#include <vector> 

int main()
{
std::vector<int> v( 1 );

std::vector<int>::iterator it = v.begin();
*it = 10;
}


Константный итератор const_iterator предназначен для работы с константными контейнерами. С помощью такого итератора нельзя изменить значение элемента последовательности. например,

#include <vector> 

int main()
{
std::vector<int> v( 1 );

std::vector<int>::const_iterator cit = v.begin();
*cit = 10; // Ошибка компиляции: нельзя менять значение элемента
}


Для получения диапазона, охватывающий начало и конец контейнера для этих двух типов итераторов существовали перегруженные функции-члены класса: begin() и end() . Если контейнер объявлен как константный, то вызываются функции begin() и end(), которые возвращают const_iterator. Если контейнер не константный, то вызываются перегруженные функции begin() и end(), которые воозвращают iterator. Например,

#include   <vector> 
#include <iterator>
#include <algorithm>
#include <iostream>
int main()
{
std::vector<int> v1( 10, 2 );

// Так как контейнер не константный, то begin() и end() возвращают iterator
std::copy( v1.begin(), v1.end(), std::ostream_iterator<int>( std::cout, " " ) );

const std::vector<int> v2( 10, 2 );

// Так как контейнер константный, то begin() и end() возвращают const_iterator
std::copy( v2.begin(), v2.end(), std::ostream_iterator<int>( std::cout, " " ) );
}


Многие алгоритмы, как, например, алгоритм std::copy, используемый в примере, не выполняют изменения значений исходной последовательности. Поэтому нет никакой необходимости использовать iterator вместо const_iteratior. Более того желательно явно указать человеку, читающему наш код, что данный алгоритм или иная функция не изменняют значения исходной последовательности, задав при этом константный итератор. Но как это сделать? Увы, удобного и простого способа это сделать до появления стандарта С++ 2011 не существовало. Теперь же в новом стандарте для контейнеров введены новые функции, задающие диапазон, с помощью которых программист может явно указать, что он будет использовать константый итератор. То есть семейство функций-членов классов контейнеров begin() и end() пополнилось их собратьями cbegin() и cend(). Теперь если мы хотим использовать именно константные итераторы для неконстантных контейнеров, мы можем это сделать с помощью этих функций. Тогда для примера выше, в котором происходило копирование элементов вектора в выходной поток, для не константного контейнера мы можем явно указать, что в алгоритме используются константные итераторы, то есть тем самым для читающего код человека мы даем понять, что данный алгоритм не будет менять значений элементов последовательности.

#include   <vector> 
#include <iterator>
#include <algorithm>
#include <iostream>
int main()
{
std::vector<int> v1( 10, 2 );

// Здесь используются функции cbegin() и cend(), возвращающие const_iterator.
// таккак алгоритм не должен менять значений элементов вектора
std::copy( v1.cbegin(), v1.cend(), std::ostream_iterator<int>( std::cout, " " ) );

const std::vector<int> v2( 10, 2 );

// Здесь используются прежние функции begin() и end(),
// так как для константного контейнера они возвращают const_iterator
// Хотя для самодокументируемости программы все же лучше использовать
// вместо них cbegin() и cend()
std::copy( v2.begin(), v2.end(), std::ostream_iterator<int>( std::cout, " " ) );
}


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

Так в чем же состоит ложка дегтя, о которой я в начале говорил? Проблема в том, что для глобальных функций begin() и end() я не нашел в рабочем проекте стандарта 2011 аналогичных функций, которые явно указывали на то, что возвращаемое значение является константным итератором. То есть не хватает глобальных функций с именами cbegin() и cend(). На мой взгляд это досадное упущение стандарта, если только в самый последний момент в заключительную редакцию стандарта эти функции не были включены. Я проверил компилятор MS VC++ 2011, но там также этих функций не оказалось. Значит, скорей всего, их действительно забыли включить в стандарт С++ 2011!

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



ссылка на сообщение  Отправлено: 28.07.12 18:24. Заголовок: Так как я рассматрив..


Так как я рассматриваю отсутствие глобальных функций cbegin() и cend() в стандарте С++ 2011 как серьезное упущение стандарта, я решил отослать в комитет по стандартизации сообщение о дефекте стандарта. И уже получил обнадеживающий ответ от William M. (Mike) Miller, что возможно мое сообщение будет рассмотрено. Так как по его мнению вопрос скорей всего должен быть адресован рабочей группе по библиотеке С++, то он решил подключить также председателя этой группы Alisdair Meredith.

Так что не исключено, что эта тема получит развитие. Возможно, что я - не первый, кто обратилл внимание на этот очевидный дефект стандарта.

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



ссылка на сообщение  Отправлено: 28.07.12 18:25. Заголовок: Будет к месту упомян..


Будет к месту упомянуть еще одно нововведение стандарта С++ 2011, когда речь идет о диапазонах последовательностей. В первом сообщении этой темы я писал, что при работе с массивами имеют место некоторые трудности при задании диапазонов для массивов. Эти трудности связаны с тем, что способ задания диапазонов для массивов выпадает из общей схемы задания диапазонов для стандартных контейнеров. Для наглядности повторю пример из первого сообщения, изменив в нем лишь список инициализации массива, чтобы элементы массива различались по значению.

#include   <iterator>   
#include <algorithm>
#include <iostream>

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

std::copy( a, a + sizeof( a ) / sizeof( *a ), std::ostream_iterator<int>( std::cout, " " ) );
}


Появление в новом стандарте С++ глобальных функций std::begin() и std::end() упростило задание диапазонов для массивов. Вышеприведенный код можно переписать следующим образом с использованием этих глобальных функций.

#include   <iterator>   
#include <algorithm>
#include <iostream>

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

std::copy( std::begin( a ), std::end( a ), std::ostream_iterator<int>( std::cout, " " ) );
}


Функции std::begin() и std::end() задают диапазон для прохода по нему в прямом направлении. А что если требуется задать диапазон, чтобы элементы массива перебирались в обратном направлении? Придется писать следующий запутанный код.

#include   <iterator>   
#include <algorithm>
#include <iostream>

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

std::copy( std::reverse_iterator<int *>( std::end( a ) ),
std::reverse_iterator<int *>( std::begin( a ) ),
std::ostream_iterator<int>( std::cout, " " ) );
}


Конечно есть стандартный алгоритм std::reverse_copy(), который делает тоже самое, задавая диапазон, привычный ддя последовательных итераторов, а не реверсивных. Но в общем случае не всегда есть в наличии готовый алгоритм, который позволяет так легко решить задачу, используя задание диапазона, характерного для последовательных итераторов. Часто программисту надо явно самому задавать реверсивные итераторы, как в приведенном выше примере, чтобы обспечить проход по элементам массива в обратном направлении.

Если бы вместо массива был бы, например, стандартный контейнер std::vector, то аналогичный пример для него выглядел бы значительно проще:

#include   <iterator>   
#include <algorithm>
#include <numeric>
#include <iostream>
#include <vector>

int main()
{
std::vector<int> v( 10 );

std::iota( v.begin(), v.end(), 0 );

std::copy( v.rbegin(), v.rend(), std::ostream_iterator<int>( std::cout, " " ) );
}


То есть тот же самый эффект, что и для массива в предыдущем примере, достигался бы за счет использования функций-членов класса rbegin() и rend().
Хорошо бы, что и для массивов были глобальные функции rbegin() и rend(). Увы, таких функций нет. И это еще одно серьезное упущение нового стандарта С++ 2011.
Но не все так плохо. Есть один простой путь исправить данный недостаток. В новом стандарте С++ 2011 появился простой класс std::array, в который можно "обернуть" массив и использовать знакомые функции-члены класса begin(), end(), rbegin() и rend().
Вот как будет выглядеть данный пример при использовании "обертки" для массива в виде стандартного класса std::array

#include   <iterator>   
#include <algorithm>
#include <iostream>
#include <array>

int main()
{
std::array<int, 10> a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

std::copy( a.rbegin(), a.rend(), std::ostream_iterator<int>( std::cout, " " ) );
}


Как видно из примера инициализация "обернутого" в класс std::array массива ничем не отличается от инициализации обычного массива. Но при этом появляются дополнительные плюсы. Вы получаете функции-члены класса для получения значений итераторов, включая и реверсивный итератор.
Есть и еще один большой плюс использования этой обертки. Вы теперь можете сравнивать массивы с помощью стандартных операторов отношения. То есть вы можете писать

if ( a == b ) std::cout << "a == b is true\n"; 
else std::cout << "a == b is false\n";


Естественно, что такое сравнение возможно, если a и b объявлены как объекты std::array с одинаковыми шаблонными аргументами. То есть можно сравнивать массивы одного типа, имеющих одинаковую размерность.

Так что, думаю, скоро класс std::array для задания массивов приобретет популярность.

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



ссылка на сообщение  Отправлено: 28.07.12 18:26. Заголовок: Сыроежка пишет: Так..


Сыроежка пишет:

 цитата:
Так как я рассматриваю отсутствие глобальных функций cbegin() и cend() в стандарте С++ 2011 как серьезное упущение стандарта, я решил отослать в комитет по стандартизации сообщение о дефекте стандарта. И уже получил обнадеживающий ответ от William M. (Mike) Miller, что возможно мое сообщение будет рассмотрено. Так как по его мнению вопрос скорей всего должен быть адресован рабочей группе по библиотеке С++, то он решил подключить также председателя этой группы Alisdair Meredith.

Так что не исключено, что эта тема получит развитие. Возможно, что я - не первый, кто обратилл внимание на этот очевидный дефект стандарта



Уже есть некоторый прогресс в продвижении моего предложения к стандарту. После того, как я это предложение направил членам комитета по стандартизации и опубликовал свое предложение в группе новостей по стандарту С++, то Дмитрием Полухиным - техгическим экспертом при комитета по стандартизации С++ было подготовлено соответсвующее предложение для изменения стандарта, которое включено в список предложений для рассмотрения комитетом по изменению и дополнению стандарта.
Я еще предложил включить в стандарт С++ 2011 глобальные функции rbegin, rend, crbegin и crend. Но пока этот вопрос на стадии предварительного обсуждения, хотя на мой взгляд их включение в стандарт было бы логически последовательным и крайнне полезным.



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



ссылка на сообщение  Отправлено: 04.10.13 23:54. Заголовок: Мои усилия по продви..


Мои усилия по продвижению обобщенных функций задания диапазона не прошли даром. Комитет по стандартизации C++ включил их в рабочий проект стандарта C++ 2014.

Вот как они выглядят в новой редакции рабочего проекта стандарта C++ 2014 в документе Working Draft, Standard for Programming C++ №3691:

 
// 24.7, range access:
template <class C> auto begin(C& c) -> decltype(c.begin());
template <class C> auto begin(const C& c) -> decltype(c.begin());
template <class C> auto end(C& c) -> decltype(c.end());
template <class C> auto end(const C& c) -> decltype(c.end());
template <class T, size_t N> T* begin(T (&array)[N]);
template <class T, size_t N> T* end(T (&array)[N]);
template <class C> auto cbegin(const C& c) -> decltype(std::begin(c));
template <class C> auto cend(const C& c) -> decltype(std::end(c));
template <class C> auto rbegin(C& c) -> decltype(c.rbegin());
template <class C> auto rbegin(const C& c) -> decltype(c.rbegin());
template <class C> auto rend(C& c) -> decltype(c.rend());
template <class C> auto rend(const C& c) -> decltype(c.rend());
template <class T, size_t N> reverse_iterator<T*> rbegin(T (&array)[N]);
template <class T, size_t N> reverse_iterator<T*> rend(T (&array)[N]);
template <class E> reverse_iterator<const E*> rbegin(initializer_list<E> il);
template <class E> reverse_iterator<const E*> rend(initializer_list<E> il);
template <class C> auto crbegin(const C& c) -> decltype(std::rbegin(c));
template <class C> auto crend(const C& c) -> decltype(std::rend(c));

.

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

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