Константы в аргументах шаблона

Среди аргументов шаблонов могут быть не только типы. Допускается использование констант в качестве аргументов шаблонов. Например, размер стека можно было бы передавать не как аргумент конструктора, а задавать в списке аргументов шаблона, и тогда можно было бы обойтись без выделения динамической памяти, воспользовавшись обычным массивом:

template< typenameT, intSIZE = 10 >
classStack

{

// ...
private:
T m_Data[ SIZE ];
T * m_pTop;
};

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

if( SIZE > 10 )

do1();

Else

do2();

Т.е, с точки зрения экземпляра шаблона, выражение SIZE > 10 является константным во время компиляции. Большинство компиляторов успешно оптимизирует код, содержащий константные выражения. Для приведенного выше примера в зависимости от значения SIZE будет выбрано первое либо второе действие, и никакой проверки условия во время выполнения происходить не будет. Результирующий фрагмент будет либо вызывать функцию do1(), либо функцию do2(), в зависимости от значения SIZE, с которым он будет инстанцирован.

Такой подход позволяет вообще не использовать отдельное выделение динамической памяти для хранения данных стека. Вместо этого создается массив нужной длины в том же блоке памяти, что и весь объект Stack. Это позволяет значительно упростить код реализации, поскольку ему больше не требуется низкоуровневое управление ресурсами. В частности, полностью пропадает необходимость в деструкторе, т.к. никаких ресурсов не выделяется. Также теряют смысл конструктор перемещения и сопутствующий ему оператор перемещающего присвоения. Отсутствие легко передаваемого ресурса делает эту функциональность аналогичной копированию, а значит, бессмысленной. Несколько упрощаются реализации операций копирования, поскольку в их обязанности теперь входит лишь перенос данных, а забота о выделении и освобождении динамической памяти опускается.

Минусом подхода является несовместимость стеков с одинаковым типом данных, но разным размером: теперь Stack< int, 10 > это другой класс, отличный от Stack< int, 5 >. Однако, это неудобство сглаживается шаблонами конструктора копий и оператора присвоения, допускающими разный размер двух сторон.

Ниже представлен полный вариант реализации такого стека:

stack_fixed_array.hpp

#ifndef _STACK_FIXED_ARRAY_HPP_

#define _STACK_FIXED_ARRAY_HPP_

#include <stdexcept>

#include <initializer_list>

//*****************************************************************************

template< typenameT, intSIZE = 10 >

classStack

{

/*-----------------------------------------------------------------*/

public:

/*-----------------------------------------------------------------*/

// Стек любого типа и размера - друг этого стека!

template< typename, int> friend classStack;

/*-----------------------------------------------------------------*/

// Конструктор по умолчанию. Размер не передается!

Stack ();

// Обобщенный конструктор по списку инициалиазторов

template< typenameU >

Stack ( std::initializer_list< U > _l );

// Обобщенный конструктор копий

template< typenameU, intOTHER_SIZE >

Stack ( constStack< U, OTHER_SIZE > & _s );

// Обобщенный оператор копирующего присвоения

template< typenameU, intOTHER_SIZE >

Stack< T, SIZE > & operator= ( constStack< U, OTHER_SIZE > & _s );

// Метод добавления значения в стек

voidpush ( constT & _value );

// Метод удаления значения с вершины стека

voidpop ();

// Метод доступа к значению на вершине стека

T & top () const;

// Метод определения пустоты стека

boolisEmpty () const;

// Метод определения заполненности стека

boolisFull () const;

/*-----------------------------------------------------------------*/

private:

/*-----------------------------------------------------------------*/

// Обыкновенный массив данных

T m_Data[ SIZE ];

// Указатель на вершину стека - одна из ячеек в m_Data

T * m_pTop;

/*-----------------------------------------------------------------*/

};

//*****************************************************************************

// Конструктор по умолчанию

template< typenameT, intSIZE >

Stack< T, SIZE >::Stack ()

{

// Вершина стека изначально указывает на начало блока

m_pTop = m_Data;

}

//*****************************************************************************

// Обобщенный конструктор по списку инициализаторов

template< typenameT, intSIZE >

template< typenameU >

Stack< T, SIZE >::Stack ( std::initializer_list< U > _l )

{

// Вершина стека изначально указывает на начало блока

m_pTop = m_Data;

// Поэлементно добавляем данные из списка инициализаторов с преобразованием U->T

for( constU & x : _l )

push( (constT & ) x );

}

//*****************************************************************************

// Обобщенный конструктор копий

template< typenameT, intSIZE >

template< typenameU, intOTHER_SIZE >

Stack< T, SIZE >::Stack ( constStack< U, OTHER_SIZE > & _s )

{

// Вершина стека изначально указывает на начало блока

m_pTop = m_Data;

// Выясняем количество фактичесик размещенных данных во втором стеке

intnActual = _s.m_pTop - _s.m_Data;

// Поэлементно копируем данные из второго стека с преобразованием U->T

for( inti = 0; i < nActual; i++ )

push( ( T ) _s.m_Data[ i ] );

}

//*****************************************************************************

// Обобщенный оператор копирующего присвоения

template< typenameT, intSIZE >

template< typenameU, intOTHER_SIZE >

Stack< T, SIZE > &

Stack< T, SIZE >::operator = ( constStack< U, OTHER_SIZE > & _s )

{

// Защита от присвоения на самого себя

if( (const void* )( this) == (const void* )( &_s ) )

return* this;

// Вершина стека изначально указывает на начало блока

m_pTop = m_Data;

// Выясняем количество фактичесик размещенных данных во втором стеке

intnActual = _s.m_pTop - _s.m_Data;

// Поэлементно копируем данные из второго стека с преобразованием U->T

for( inti = 0; i < nActual; i++ )

push( _s.m_Data[ i ] );

// Возвращаем ссылку на себя

return* this;

}

//*****************************************************************************

// Реализация метода добавления значения в стек

template< typenameT, intSIZE >

voidStack< T, SIZE >::push ( constT & _value )

{

// Стек не должен быть заполнен на 100% в данный момент

if( isFull() )

throwstd::logic_error( "Stack overflow error" );

// Размещаем новое значение в стеке и увеличиваем указатель-вершину

* m_pTop++ = _value;

}

//*****************************************************************************

// Реализация метода удаления значения с вершины стека

template< typenameT, intSIZE >

voidStack< T, SIZE >::pop ()

{

// Стек не должен быть пустым в данный момент

if( isEmpty() )

throwstd::logic_error( "Stack underflow error" );

// Уменьшаем указатель-вершину

m_pTop--;

}

//*****************************************************************************

// Реализация метода доступа к значению на вершине стека

template< typenameT, intSIZE >

T & Stack< T, SIZE >::top () const

{

// Стек не должен быть пустым в данный момент

if( isEmpty() )

throwstd::logic_error( "Stack is empty" );

// Возвращаем ссылку на значение, находящееся под указателем-вершиной

return*( m_pTop - 1 );

}

//*****************************************************************************

// Реализация метода определения пустоты стека

template< typenameT, intSIZE >

Наши рекомендации