Что такое traits? Страница 2.
|
Страница 2 из 4
Тогда объекты нашего динамического массива будем создавать так: vector<std::string> vec1; // эквивалентно: vector<std::string, elem_traits<std::string> > // для всех типов, для которых не сделана специализация, // все остается как прежде: // тогда: // arg_type = const std::string& (по ссылке) // reference = std::string& // const_reference = const std::string&
// а для типа char мы сделали специализацию vector<char> vec2; // эквивалентно: vector<char, elem_traits<char> > // тогда: // arg_type = const char (по значению, а не по ссылке) // reference = char& // const_reference = const char&
| Давайте теперь рассмотрим случай, когда мы хотим создать динамический массив с элементами типа char, но чтобы arg_type был эквивалентен char&. Специализация нашего класса elem_traits для char уже существует, то есть ее сделать мы уже не можем. В таком случае остается создать новый класс свойств: // обратите внимание: структура, а не класс // (разницы никакой, это лишний раз подчеркивается данным примером) struct char_elem_traits { // здесь переопределеяем тип аргумента функций. // Мы договорились, что это будет char& typedef char& arg_type; // определили тип, который передает по ссылке типы char
// остальное оставляем так же. // Хотя ничто не мешает нам переопределеить тип ссылки или константной ссылки typedef char& reference; typedef const char& const_reference; };
| Тогда осталось только создать нужный динамический массив: vector<char, char_elem_traits> vec; // тогда: // arg_type = char& (по ссылке, но не по константной) // reference = char& // const_reference = const char&
| Но заметим, что класс(структура) char_elem_traits переопределяет только один тип - arg_type, а остальные остаются неимзенными по отношению к elem_traits<char>. То есть мы произвели лишнюю работу, определив самостоятельно типы reference и const_reference. Хорошо еще, что тут немного типов, а представьте, что их было бы около 20? 50? Чтобы каждый раз не переписывать общие свойства, можно воспользоваться открытым наследованием и переопределить нужные нам типы: class char_elem_traits : public elem_traits<char> { public: typedef char& arg_type; // определили тип, который передает по ссылке типы char
// типы reference и const_reference наследуются };
| Теперь можно использовать наш класс свойств char_elem_traits точно так же, как мы делали это раньше.
До этого мы рассматривали только свойства, определяющие необходимые типы. Еще могут быть свойства-значения: они предоставляют нужные константы для данного типа. Рассмотрим пример: нам надо написать шаблон класса, которому для каждого параметра шаблона(типа) требуются связанные с ним константы. Первая мысль будет такой(пример немного перефразирован из вопроса с форума): template <typename T, std::size_t T2 = sizeof(T) * 4, std::size_t T3 = sizeof(T) * 2, std::size_t T4 = sizeof(T) // ... > class X { // используем нужные константы // T2 == sizeof(T) * 4 // T4 == sizeof(T) };
| Но мы теперь люди продвинутые и знаем, как избежать такого большого количества аргументов шаблона - обернуть все в traits: template <typename T> struct x_traits { static std::size_t T2 = sizeof(T) * 4; static std::size_t T3 = sizeof(T) * 2; static std::size_t T4 = sizeof(T); };
template <typename T, typename traits = x_traits<T> > class X { // используем нужные константы через traits: // traits::T2 == sizeof(T) * 4 // traits::T4 == sizeof(T) };
| Но теперь в нашем коде возникает проблема: если вдруг получится так, что пользователь нашего класса захочет взять адрес нашей константы, компилятор должен будет создать реальную константу в памяти, адрес которой можно взять. Для этого был предуман трюк с enum'ом: template <typename T> struct x_traits { enum { T2 = sizeof(T) * 4 }; enum { T3 = sizeof(T) * 2 }; enum { T4 = sizeof(T) }; };
template <typename T, typename traits = x_traits<T> > class X { // используем нужные константы через traits: // traits::T2 == sizeof(T) * 4 // traits::T4 == sizeof(T) };
| |