2013-11-08 2 views
2

Классы всех признаков, такие как std::iterator_traits, полезны, отделяя свойства типа от его определения, так что, например, свойства могут быть доступны до завершения определения.Унаследованные (или членские) черты idiom

Определяющие черты в дополнение к каждому классу клиента неудобны, потому что черты обычно также имеют место в качестве членов. Вот почему общая реализация std::iterator_traits определена в терминах ее членов шаблона аргумента.

template< typename it > 
struct iterator_traits { 
    typedef typename it::category category; 
    typedef typename it::value_type value_type; 
    // etc 
}; 

Разве не проще и меньше работать для компилятора, вместо этого использовать наследование?

Это не позволяет документировать интерфейс в основном шаблоне, но в любом случае есть другие возможности.

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


Возможно, это полностью назад. В дополнение к std::iterator_traits у нас также есть std::iterator, псевдо-абстрактный базовый класс, в основном с теми же членами. Такая избыточность - это запах кода. Не лучше ли, чтобы пользовательские итераторы выглядели так?

template<> 
struct iterator_traits< struct my_iterator > { 
    typedef random_access_iterator_tag category; 
    typedef foo value_type; 
    ... 
}; 
struct my_iterator : iterator_traits< struct my_iterator > { 
    ... 
}; 

(Ради аргумента, давайте игнорировать тот факт, что фактическая std::iterator_traits специализация должна быть объявлена ​​в namespace std. Я пытаюсь сделать привычную иллюстрацией чего-то, что может произойти в пользовательском коде.)

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

Концептуально лучше отделить качества класса от реализации его услуг независимо от того, необходимо ли это разделение. НО, этот стиль требует разбиения каждого клиентского класса на две части, включая явную специализацию, которая является своего рода уродливой.


Кто-нибудь знаком с этим дизайнерским пространством? Я склоняюсь к второму идиому, хотя на практике это выглядит необычно. Но есть, вероятно, и те, и другие, известные тем, кто раньше здесь жил.

ответ

1

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

Альтернативы 1 и 2 могут быть объединены в лучшее из обоих миров шаблон, который

  • всегда обеспечивает оптимальное разделение задач (путем разделения класса на черты характера и реализации)
  • не требуют не разделив класс
  • никогда не требует открытия пространство имен библиотеки

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

template< typename t > 
t traits_type_entry(t const &); // Declared, never defined. 

template< typename t > 
using traits_type = decltype(traits_type_entry(std::declval<t>())); 

По умолчанию T служит его собственные черты типа как traits_type<T>::type является T. Чтобы изменить это для заданного типа t классу свойств t_traits, объявите (но не определите) функцию t_traits traits_type_entry(t const &). Этот класс t_traits может быть или не быть базовым классом t; объект traits_type не заботится. Поскольку функция будет найдена с помощью поиска аргумента-depenedent, она может быть объявлена ​​как функция друга без объявления в области пространства имен.

Использование, вложенное внутри класса (только для сложного тестового случая), выглядит следующим образом. Для обычного использования в пространстве имен просто отбросьте ключевое слово friend.

class outer_scope { 
    struct special; 
    struct special_traits { 
     typedef int value_type; 
     constexpr static int limit = 5; 
    }; 
    friend special_traits traits_type_entry(special const &); 

    struct unspecial { 
     typedef double baz_type; 
     int table[ util::traits_type<special>::limit ]; 
    }; 

    struct special : special_traits { 
     void f() { 
      std::pair< typename util::traits_type<unspecial>::baz_type, 
         value_type >(); 
     } 
    }; 
}; 

http://ideone.com/QztQ6i

Обратите внимание, параметр, traits_type_entryt const & может быть просто t до тех пор, как класс копируемый и разрушаемость.

Кроме того, вы можете предотвратить объявление объекта типа (не подгоняемого) типа, если первичный шаблон возвращает тип, производный от t, с его удаленным конструктором, а не t.