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

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



ссылка на сообщение  Отправлено: 28.07.12 17:12. Заголовок: Эволюция адаптеров контейнеров в новом стандарте С++


Сегодня занимался стандартным адаптером контейнеров std::stack, и у меня возник напрашивающийся вопрос: почему у адаптеров контейнеров в стандарте С++ 2003 нет члена функции swap? Ведь использовать общий стандартный алгоритм std::swap для адаптеров контейнеров вместо функций-членов swap, лежащих в их основе контейнеров, не эффективно.
Сказано -сделано, и я решил реализовать функцию-член класса для адаптера контейнеров std::stack.

Код выглядел следующим образом. Естественно, я свой код вводил в собственном пространстве имен usr::, чтобы не было коллизий со стандартным пространством имен std::

namespace usr 
{

template <typename T, typename Container = std::deque<T> >
class stack
{
protected:
Container c;
...
public:
void swap( stack<T, Container> &x )
{
swap( c, x.c );
}
...
};

template <typename T, typename Container>
void swap( stack<T, Container> &x, stack<T, Container> &y
{
x.swap( y );
}

} // end of namespace usr


Далее в main я решил проверить, что получилось

int main() 
{
typedef usr::stack<int>::container_type Container;

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

usr::stack<int> s1( Container( a, a + sizeof( a ) / sizeof( *a ) ) );
usr::stack<int> s2;

swap( s1, s2 );

return ( 0 );
}


Но код упорно не желал компилироваться! Ошибка на самом деле не совсем очевидная. Дело в том, что при объявлении члена-функции swap в следствии алгоритма поиска имен в С++ получилось так, что вызов внутренней функции swap компилятор рассматривает как рекурсивный вызов этого же члена-функции и, естественно сообщает о том, что количество параметров слишком большее.

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

   void swap( stack<T, Container< &x ) 
{
swap( c, x.c );
}


Осознание этой ошибки отняло у меня достаточно время.

Ошибка исправляется просто. Нужно было указать, что на этот раз вызывается функция swap из пространства имен std::

   void swap( stack<T, Container> &x ) 
{
std::swap( c, x.c );
}


Но самый главный вывод, который я сделал, состоял в том, что это было крайне странно и неестественно, что в стандарте С++ 2003 года для адаптеров контейнеров отсутствовала функция-член класса swap. На мой взгляд это очень серьезное и явное упущение!

Из любопытства я решил заглянуть в новый стандарт С++ 2011. Неужели данную оплошность так и не исправили?!

Нj к своему удовлетворению я увидел, что, наконец-то, функция-член класса swap в новом стандарте добавлена! Она выглядет следующим образом

void swap(stack& s) { using std::swap; swap(c, s.c); }


Отличие от моей версии состоит в том, что здесь решили воспользоваться директивой using вместо явного указания имени пространства имен непосредственно перед вызовом функции swap, как это сделал я.

Создается впечатление, что разработчики нового стандарта читают мои мысли!:)

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





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


Почему на самом деле важна функция swap для адаптеров контейнеров? Дело не только в том, что может возникнуть потребность обменять содержимое двух адаптеров контейнеров. Проблема заключается еще в том, что если, например, в качестве базового контейнера для адаптера контейнера stack используется контейнер std::vector, то использование функции swap - это, практически, единственный эффективный способ "ужать" вектор. Известно, что векторы постоянно "растут", а метод resize не уменьшает емкость контейнера, а лишь позволяет ее увеличить. Поэтому для векторов используется следующий прием.

#include   <iostream> 
#include <vector>


int main()
{
const int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::vector<int> v( a, a + sizeof( a ) / sizeof( *a ) );

std::cout << "v.capacity() = " << v.capacity() << std::endl; // Мой компилятор выводит значение 10

v.push_back( 11 );

std::cout << "v.capacity() = " << v.capacity() << std::endl; // Здесь выводится значение 16

std::vector<int>( v ).swap( v );

std::cout << "v.capacity() = " << v.capacity() << std::endl; // Вектор ужат. Значение capacity = 11

return ( 0 );
}



Главным в этом приеме сжатия вектора является вызов функции-члена класса swap для временного объекта типа std::vector, который создается путем копирования элементов из вектора, который нужно ужать. Затем содержимое временного вектора и исходного вектора обменирваются Этот прием описан в книге Герба Саттера "Решение сложных задач на С++".

   std::vector<int>( v ).swap( v );


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

Разработчики компилятора MS VC++ 2010 предвосхитили выпуск нового стандарта С++ 2011 и заранее в свои адаптеры контейнеров включили функцию-член класса swap. Поэтому те, у кого есть этот компилятор, могут проверить ее действие у адаптеров контейнеров. Кроме того Microsoft также включили в этот класс функцию доступа к базовому класса _Get_container. Этой функции в стандарте С++ 2011 года нет, но можно ее воспользоваться для проверки работы функции -члена класса swap

Итак, те, у кого есть MS VC++ 2010, могут выполнить следующий тест.

#include   <iostream> 
#include <vector>
#include <stack>


int main()
{
typedef std::stack<int, std::vector<int> >::container_type Container;
const int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::stack<int, vector<int> > s( Container( a, a + sizeof( a ) / sizeof( *a ) ) );

std::cout << "s._Get_container().capacity() = " << s._Get_container().capacity() << std::endl; // Мой компилятор выводит значение 10

s.push( 11 );

std::cout << "s._Get_container().capacity() = " << s._Get_container().capacity() << std::endl; // Здесь выводится значение 16

std::stack<int, vector<int> >( s ).swap( s );

std::cout << "s._Get_container().capacity() = " << s._Get_container().capacity() << std::endl; // Вектор ужат. Значение capacity = 11

return ( 0 );
}


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


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



ссылка на сообщение  Отправлено: 28.07.12 17:13. Заголовок: К сожалению приходит..


К сожалению приходится константировать, что новый стандарт С++ также является некоторым суррогатом! То есть сделано лишь полшага в развитии адаптеров контейнеров. Для полного описания интерфейса, например, адаптера контейнера std::stack не хватает по крайней мере еще двух функций. Это функции, которая сообщает максимально допустимый размер стека

size_type max_size() const;


и функции удаление элементов из стека

void clear();


Отсутствие этих функций у адаптеров контейнеров - это серьезное упущение.

Кроме того можно было бы ввести операторы ввода и вывода, как для потоков: << и >>.

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



ссылка на сообщение  Отправлено: 03.04.13 21:38. Заголовок: Еще один серьезный н..


Еще один серьезный недостаток адаптера контейнеров std::stack заключается в отсутствии конструктора, принимающего список инициализации. Для простых задач было бы удобно инициализировать стек во время его объявления. Например,

std::stack<int> s = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


Однако, увы, этот код не будет компилироваться, так как соответствующего конструктора у класса std::stack нет.

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

 #include <stack> 
#include <iostream>

int main()
{
std::stack<int> s( std::deque<int>( { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ) );

while ( !s.empty() )
{
std::cout << s.top() << ' ';
s.pop();
}
std::cout << std::endl;
}


Однако, согласитесь, такое объявление объекта класса std::stack выглядет более запутанным и менее наглядным по сравнению с предложением,

std::stack<int> s = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


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

Я уже обратил внимание Комитета по стандартизации C++ на этот дефект в описании класса std::stack, который, кстати сказать, имеет место и у других адаптеров контейнеров, как, например, std::queue и std::priority_queue.

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

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

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