BoolStack< T >::isFull () const

{

return( m_pTop - m_pData ) == m_size;

}

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

#endif // _STACK_HPP_

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

test_stack.cpp

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

#include"stack.hpp"

#include<string>

#include<cassert>

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

intmain ()

{

// Стек целых чисел

Stack< int> s1;

s1.push( 5 );

assert( ! s1.isEmpty() && s1.top() == 5 );

// Стек действительных чисел

Stack< double> s2;

s2.push( 2.5 );

assert( ! s2.isEmpty() && s2.top() == 2.5 );

// Стек объектов-строк

Stack< std::string > s3;

s3.push( "Hello" );

assert( ! s3.isEmpty() && s3.top() == "Hello" );

// Даже стек стеков целых чисел!

Stack< Stack< int> > s4;

s4.push( Stack< int >() );

s4.top().push( 5 );

assert( ! s4.isEmpty() && !s4.top().isEmpty() && s4.top().top() == 5 );

}

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

В таком варианте реализации невозможно присвоение между экземплярами Stack< int> и Stack< double>, поскольку после инстанцирования они являются разными несвязанными классами. Однако для контейнеров значений такое поведение может быть весьма полезным на практике, разумеется, с преобразованием типа хранящихся значений. Чтобы разрешить такое копирование, нужно усовершенствовать конструктор копий, сделав его шаблоном-членом (member template). Аналогичный прием можно применить к конструктору, принимающему список инициализаторов, чтобы получить возможность создания стека на основе конвертируемых данных другого типа, например, создать стек целых чисел по массиву действительных.

В заголовочной части объявление конструктора копий и оператора копирующего присвоения следует видоизменить, а также объявить любой другой экземпляр того же класса другом для удобного доступа к его private-части:

template< typenameT >

classStack

{

//------------------------------------------------------------------------

public:

// ...

// Объявляем любой экземпляр Stack другом любого другого экземпляра Stack

template< typename> friend classStack;

// Конструктор по обобщенному списку инициалиазторов

template< typenameU >

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

// Конструктор копий - шаблон-член, ожидает тип U, потенциально U != T

template< typenameU >

Stack ( constStack< U >& _s );

// ...

// Оператор копирующего присвоения - также шаблон-член

template< typenameU >

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

// ...

};

Видоизмененная реализация будет выглядеть следующим образом:

template< typenameT >

template< typenameU > // Да, два раза template, это не ошибка!

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

: Stack( _l.size() )

{

// Перебираем список инициализаторов данных типа U

for( constU & x : _l )

// Помещаем в стек данные типа T путем преобразования U к T

push( ( constT & ) x );

}

template< typenameT >

template< typenameU > // Да, два раза template, это не ошибка!

Stack< T >::Stack ( constStack< U > & _s )

: m_size( _s.m_size )

{

// Выделяем массив для хранения данных стека

m_pData = newT[ m_size ] ;

m_pTop = m_pData;

// Поочередно вставлем элементы

intnActual = _s.m_pTop - _s.m_pData;

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

push( _s.m_pData[ i ] ); // неявное преобразование типа от U к T

}

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

template< typenameT >

template< typenameU > // Да, два раза template, это не ошибка!

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

{

// Защита от присвоения на самого себя - несколько усложняется,

// т.к. нельзя просто сравнивать адреса двух разных классов Stack<T> и Stack<U>!

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

return* this;

// Освобождаем старый блок и выделяем новый

delete[] m_pData;

m_size = _s.m_size;

m_pData = newT[ m_size ];

// Копируем полезные данные из другого стека

intnActual = _s.m_pTop - _s.m_pData;

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

m_pData[ i ] = _s.m_pData[ i ]; // неявное преобразование типа от U к T

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

m_pTop = m_pData + nActual;

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

return* this;

}

Теперь желаемое преобразование становится возможным:

test_stack_conversions.cpp

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

#include "stack.hpp"

#include <string>

#include <cassert>

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

intmain ()

{

// Создаем стек целых чисел на основе массива действительных чисел

Stack< int> s = { 1.5, 2.5, 3.5 };

assert( ! s.isEmpty() && s.top() == 3 ); // 3.5 округлится до 3

// Стек действительных чисел

Stack< double> s1;

s1.push( 2.5 );

// Создаем стек целых чисел на основе стека действительных чисел!

Stack< int> s2 = s1;

assert( !s2.isEmpty() && s2.top() == 2 ); // 2.5 округлится до 2

// Стек строк в стиле C

Stack< const char* > s3;

s3.push( "Hello" );

// Присваиваем стеку строк std::string,

// неявно конструируя такие объекты из строкового литерала

Stack< std::string > s4;

s4 = s3;

assert( !s4.isEmpty() && s4.top() == "Hello" );

}

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

Следует отметить, что такой трюк не подходит для конструкторов перемещения и оператора перемещающего присвоения. Внутреннее представление стеков для разных типов-аргументов отличается, и нельзя просто “забрать” внутреннее представление другого не связанного объекта.

При необходимости, шаблоны-члены могут существовать в обычных классах:

#include <iostream>

classTest

{

public:

template< typenameT >

voidf ( constT & _val )

{

std::cout << _val << std::endl;

}
};

intmain ()

{

Test t;

t.f( 5 ); // вызов Test::f< int>

t.f( 2.5 ); // вызов Test::f< double>
}

Каждый уникальный тип аргумент породит новый метод в классе Test.

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