В чем разница между std:: transform и std:: for_each?
Оба могут использоваться для применения функции к ряду элементов.
На высоком уровне:
-
std::for_each
игнорирует возвращаемое значение функции и
гарантирует порядок исполнения.
-
std::transform
присваивает возвращаемое значение итератору и делает
не гарантирует порядок выполнения.
Когда вы предпочитаете использовать один против другого? Есть ли какие-то тонкие оговорки?
Ответы
Ответ 1
std::transform
совпадает с map
. Идея состоит в том, чтобы применить функцию к каждому элементу между двумя итераторами и получить другой контейнер, состоящий из элементов, возникающих в результате применения такой функции. Вы можете использовать его для, например, проецирования элемента данных объекта в новый контейнер. В дальнейшем std::transform
используется для преобразования контейнера std::string
в контейнер std::size_t
s.
std::vector<std::string> names = {"hi", "test", "foo"};
std::vector<std::size_t> name_sizes;
std::transform(names.begin(), names.end(), std::back_inserter(name_sizes), [](const std::string& name) { return name.size();});
С другой стороны, вы выполняете std::for_each
для единственных побочных эффектов. Другими словами, std::for_each
очень похож на простой цикл for
на основе диапазона.
Вернемся к примеру строки:
std::for_each(name_sizes.begin(), name_sizes.end(), [](std::size_t name_size) {
std::cout << name_size << std::endl;
});
В самом деле, начиная с С++ 11, то же может быть достигнуто с помощью обозначения терминов с использованием циклы for
на основе диапазона:
for (std::size_t name_size: name_sizes) {
std::cout << name_size << std::endl;
}
Ответ 2
Ваш обзор высокого уровня
-
std::for_each
игнорирует возвращаемое значение функции и гарантирует порядок выполнения. -
std::transform
присваивает возвращаемое значение итератору и не гарантирует порядок выполнения.
в значительной степени покрывает его.
Другой способ взглянуть на него (предпочесть один над другим);
- Получают ли результаты (возвращаемое значение) операции?
- Является ли операция для каждого элемента методом члена без возвращаемого значения?
- Существуют ли два диапазона ввода?
Еще одна вещь, о которой нужно помнить (тонкое предостережение) - это изменение требований операций std::transform
до и после С++ 11 (из en.cppreference.com);
- До С++ 11 они должны были "не иметь побочных эффектов",
- После С++ 11 это изменилось на "не должно отменять никаких итераторов, включая конечные итераторы, или изменять любые элементы используемых диапазонов"
В основном это должно было обеспечить неопределенный порядок выполнения.
Когда я использую один над другим?
Если я хочу манипулировать каждым элементом в диапазоне, я использую for_each
. Если мне нужно вычислить что-то из каждого элемента, я бы использовал transform
. При использовании for_each
и transform
я обычно соединяю их с лямбдой.
Тем не менее, я считаю, что мое текущее использование традиционного for_each
несколько уменьшилось с момента появления диапазонов на основе for
и lambdas в С++ 11 (for (element : range)
). Я считаю, что его синтаксис и реализация очень естественны (но ваш пробег здесь будет отличаться) и более интуитивно понятным для некоторых случаев использования.
Ответ 3
Хотя на этот вопрос был дан ответ, я считаю, что этот пример еще раз пояснит разницу.
for_each
относится к не изменяющим STL операциям, что означает, что эти операции не меняют элементы коллекции или самой коллекции. Поэтому значение , возвращаемое for_each, всегда игнорируется и не присваивается элементу коллекции.
Тем не менее, по-прежнему можно изменять элементы коллекции, например, когда элемент передается функции f с использованием ссылки. Следует избегать такого поведения, поскольку оно не согласуется с принципами STL.
В отличие от этого функция transform
относится к модификации операций STL и применяет заданные предикаты (unary_op или binary_op) к элементам коллекции или коллекций и сохраняет результаты в другой коллекции.
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
void printer(int i) {
cout << i << ", ";
}
int main() {
int mynumbers[] = { 1, 2, 3, 4 };
vector<int> v(mynumbers, mynumbers + 4);
for_each(v.begin(), v.end(), negate<int>());//no effect as returned value of UnaryFunction negate() is ignored.
for_each(v.begin(), v.end(), printer); //guarantees order
cout << endl;
transform(v.begin(), v.end(), v.begin(), negate<int>());//negates elements correctly
for_each(v.begin(), v.end(), printer);
return 0;
}
который будет печатать:
1, 2, 3, 4,
-1, -2, -3, -4,
Ответ 4
Реальный пример использования std:: tranform - это когда вы хотите преобразовать строку в верхний регистр, вы можете написать такой код:
std::transform(s.begin(), s.end(), std::back_inserter(out), ::toupper);
если вы попытаетесь достичь того же с помощью std:: for_each, например:
std::for_each(s.begin(), s.end(), ::toupper);
Он не конвертирует его в верхнюю строку