У меня есть код, в котором концептуально мои входные данные представляют собой некоторый контейнер из Foo
объектов. Код «обрабатывает» эти объекты один за другим, и желаемый результат — заполнить контейнер FooProduct
объектами результатов.
Мне нужен только один проход через входной контейнер. «Обработка» выполняется с сохранением состояния (это не std::transform()
), а количество объектов результата не зависит от количества объектов ввода.
Навскидку я увидел здесь два очевидных способа определения API.
Самый простой способ сделать это — жестко запрограммировать конкретный тип контейнера. Например, я мог бы решить, что ожидаю vector
параметров, например:
void ProcessContainerOfFoos(const std::vector<Foo>& in, std::vector<FooProduct>&out);
Но на самом деле у меня нет причин ограничивать клиентский код определенным типом контейнера. Вместо того, чтобы ограничивать типы параметров только vector
, я мог бы сделать метод универсальным и использовать итераторы в качестве параметров шаблона:
/**
* @tparam Foo_InputIterator_T An input iterator giving objects of type Foo.
* @tparam FooProduct_OutputIterator_T An output iterator writing objects
* of type FooProduct.
*/
template<typename Foo_InputIterator_T, typename FooProduct_OutputIterator_T >
void ProcessContainerOfFoos(Foo_InputIterator_T first, Foo_InputIterator_T last,
FooProduct_OutputIterator_T out);
Я спорю между этими двумя формулировками.
Соображения
Мне первый код кажется "проще", а второй "правильнее":
- Нешаблонные типы делают подпись более четкой; Мне не нужно объяснять в документации, какие типы использовать и каковы ограничения на параметр шаблона.
- Без шаблонов я могу скрыть реализацию в файле .cpp; с шаблонами мне нужно будет представить реализацию в заголовочном файле, заставив клиентский код включать все, что мне нужно для фактической логики обработки.
- Шаблонная версия выглядит так, как будто она более четко выражает мое намерение, потому что я предпочел бы быть безразличным к тому, какой тип контейнера используется.
- Шаблонная версия является более гибкой и тестируемой — например, в моем коде я могу использовать некоторую пользовательскую структуру данных
MySuperEfficientVector
, но я все равно смогу протестироватьMyFooProcessor
без какой-либо зависимости от пользовательского класса.
Помимо субъективного выбора с учетом этих соображений, есть ли основная причина выбрать один из них вместо другого? Точно так же есть ли лучший способ создать этот API, который я упустил? сильный>
std::vector<FooProduct>
по значению из функции, чтобы воспользоваться преимуществами семантики перемещения. - person NathanOliver   schedule 22.01.2015template<typename Foo_InputIterator_T> std::vector<FooProduct> ProcessContainerOfFoos(Foo_InputIterator_T first, Foo_InputIterator_T last);
? Я не понимаю, в чем тут преимущество. Чем перемещение вектора лучше, чем использование выходного итератора? - person Ziv   schedule 22.01.2015std::vector<FooProduct> foo = ProcessContainerOfFoos(bar.begin(), bar.end());
. Поскольку мы можем использовать семантику перемещения при возврате по значению, производительность при этом не снижается. - person NathanOliver   schedule 22.01.2015