Неправильно ли использовать шаблоны с неявным предположением, что будут определены определенные функции-члены параметризованного типа?

Скажи, что ты пишешь действительно плохой класс

template <typename T>
class IntFoo
{
T container ;
public:
void add( int val )
{
// made an assumption that
// T will have a method ".push_front".
container.push_front( val ) ;
}
} ;

Игнорировать тот факт, что класс предполагает, что контейнер будет something<int>Вместо этого обратите внимание на то, что

IntFoo< list<int> > listfoo ;
listfoo.add( 500 ) ; // works

IntFoo< vector<int> > intfoo;
//intfoo.add( 500 ) ; // breaks, _but only if this method is called_..

В общем, это нормально вызывать функцию-член параметризованного типа, как это? Это плохой дизайн? У этого (анти) паттерна есть имя?

5

Решение

Это прекрасно, и называется «утка» во время компиляции и используется во всех местах стандартной библиотеки. А если серьезно, как бы вы сделали что-нибудь полезное с шаблоном, не принимая аргумент шаблона для поддержки определенных функций?

Давайте посмотрим на любой алгоритм в stdlib, например, std::copy:

template<class InIt, class OutIt>
OutIt copy(InIt first, Init last, OutIt out){
for(; first != last; ++first)
*out++ = *first;
return out;
}

Здесь объект типа InIt предполагается поддержать operator*() (для косвенного обращения) и operator++() для продвижения итератора. Для объекта типа OutItпредполагается поддержать operator*() а также и operator++(int), Общее предположение также заключается в том, что все, что возвращается из *out++ присваивается (иначе конвертируемый) из любого *first доходность. Еще одним предположением будет то, что оба InIt а также OutIt являются копируемыми.

Другое место, где это используется, находится в любом стандартном контейнере. В C ++ 11, когда вы используете std::vector<T>, T должен быть копируемым если и только если Вы используете любую функцию-член, для которой требуется копия.

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

template<class InIt, class UnaryFunction>
InIt for_each(InIt first, InIt last, UnaryFunction f){
for(; first != last; ++first)
f(*first);
return first;
}

InIt предполагается, чтобы поддерживать те же операции снова, как в copy пример выше. Тем не менее, теперь у нас также есть UnaryFunction, Предполагается, что объекты этого типа поддерживают запись вызова функции после исправления, особенно с один аргумент (Унарный). Далее предполагается, что этот параметр этого вызова функции может быть преобразован из любого *first доходность.

Типичный пример использования этого алгоритма — простая функция:

void print(int i){ std::cout << i << " "; }

int main(){
std::vector<int> v(5); // 5 ints
for(unsigned i=0; i < v.size(); ++i)
v[i] = i;
std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}

Тем не менее, вы также можете использовать функциональные объекты для этого — определенный пользователем тип, который перегружает operator():

template<class T>
struct generate_from{
generate_from(T first) : _acc(first) {}
T _acc;
void operator()(T& val){ val = _acc++; }
};

int main(){
std::vector<int> v(5); // 5 ints
// yes, there is std::iota. shush, you.
std::for_each(v.begin(), v.end(), generate_from<int>(0)); // fills 'v' with [0..4]
std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}

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

  • копируемый (в ctor)
  • постинкрементный (в operator())
  • копируемый (в operator())
12

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]