Пожалуйста, поймите, что я все еще изучаю концепции семантики перемещения и значения в C ++ 11. Мой вопрос заключается в том, может ли унаследованный код получить бесплатный обед, избегая ненужного копирования, просто используя компиляторы C ++ 11 и STL.
Вот очень простой пример. Этот код создает простую таблицу частот символов для данной строки. Например, «яблоко» должно вернуться {('a', 1), ('e', 1), ('l', 1), ('p', 2)}
, Как вы увидите, я просто использую векторы в качестве значений.
typedef std::tuple<char, int> Frequency;
typedef std::vector<Frequency> Frequencies;
Frequencies buildFrequenciesTable(std::string w) {
char table['z' - 'a' + 1] = { 0, };
std::for_each(w.cbegin(), w.cend(), [&table](char c) {
++table[::tolower(c) - 'a'];
});
Frequencies freqs;
for (size_t i = 0; i < 'z' - 'a' + 1; ++i) {
if (table[i] != 0)
freqs.push_back(tuple<char, int>((char) ('a' + i), table[i]));
}
return freqs; // Q1: Is vector get copied?
}
int main() {
using namespace std;
Frequencies f1 = buildFrequenciesTable("apple"); // Q2: Copy?
Frequencies f2 = buildFrequenciesTable("banana");
vector<Frequencies> fs = { f1, f2 }; // Q3: Copy?
}
Ясно, что C ++ 03 генерирует весь код копирования (используя конструкторы копирования и операторы присваивания) при возврате векторов в качестве значения. Как насчет C ++ 11? std::vector
конструктор ходов. Может ли этот код избежать ненужных копий? Или я должен использовать либо &&
или же std::forward
в приведенном коде?
Я пытался отладить внутренний код STL, но это было трудно убедить.
Примечание. Моя цель — свести к минимуму ненужные копии этих функций. Я знаю, что мог бы использовать новые / указатели / ссылки, но это должно решить проблему утечки памяти. Итак, я хотел бы использовать значения как можно больше.
Для Q1, скорее всего, нет копии даже в C ++ 03, поскольку копия удаляется с помощью «Оптимизации именованных возвращаемых значений» (NRVO).
Для Q2 также, скорее всего, нет копии даже в C ++ 03, поскольку copy-elision удаляет ее.
Для Q3, даже в C ++ 11 вы делать есть копии, которые нужно пометить f1
а также f2
как подвижные для того, чтобы фактически переместить их:
vector<Frequencies> fs = { std::move(f1), std::move(f2) };
Так как вы задали несколько вопросов, я думаю, что я опущу дальнейшие объяснения, посмотрите NRVO, copy-elision и где std::move
требуется, спросите, если у вас есть какие-либо дополнительные вопросы.
Однако в некоторых случаях вы можете получить бесплатные ходы, например, если есть временный объект, который можно переместить:
vector<Frequencies> fs = { buildFrequenciesTable("apple"),
buildFrequenciesTable("bananas") };
Вышеуказанное обнаружит два вектора, возвращенные из buildFrequenciesTable()
как временные и, следовательно, они будут перемещены в fs
,
Возврат вектора из функции (Q1
) будет использовать семантику перемещения, где это возможно, без необходимости изменения кода. Аналогично, инициализация векторов из возвращенного временного (Q2
) будет использовать семантику перемещения; возвращаемое значение Rvalue, так можно перенести с. На практике оба хода (или, исторически, копии) должны быть исключены, чтобы инициализировать функцию f1
а также f2
непосредственно без перемещения или копирования.
Положить их в вектор (Q3
) требует копирования: переменные lvalues, который не может быть неявно перемещен. Так что вам придется использовать std::move
или реструктурируйте код, чтобы избежать этих копий.