#c #constructor #stdarray #return-value-optimization
Вопрос:
Я знаю std::array
, что семантика перемещения не используется, потому что она не выделяется динамически. Делают ли компиляторы правильный NRVO для этого? Как насчет в контексте того, что вызывающий код является списком инициализаторов конструктора?
Код ниже и на голдболте здесь: https://godbolt.org/z/je1Mvj15P NB Проходит ссылка на общий argv[1] = "12345678901234567890"
доступ . Кроме того, он использует только-Og, чтобы сборка была читаемой. В -O3 он начинает разворачивать петли и т. Д., Но это не влияет на вопрос, который я считаю.
У вызова списка инициализации конструктора arr_(get_arr(s))
нет другого выбора, кроме как скопировать, потому что перемещение недоступно. Если только компилятор не выполняет полное NRVO (что «не обязательно», см. Комментарии ниже).
Вывод проводника компилятора, похоже, не показывает копирования в моих глазах.
Спасает ли это NRVO? Это идиоматично / хорошо? Или это std::array
неправильный выбор здесь? Или, может быть, этот способ инициализации неподвижного агрегатного элемента с помощью вызова функции и, следовательно, полагаться на RVO неразумно/надежно?
Было бы лучше оставить arr_
неинициализированный в списке инициализации конструктора и переместить код из get_arr
в тело конструктора? Вот так: https://godbolt.org/z/jxv69YvK3
#include <algorithm>
#include <array>
#include <numeric>
#include <string>
std::array<std::byte, 20> get_arr(const std::stringamp; s) {
std::array<std::byte, 20> a;
// a silly proxy algorithm for the real thing
std::transform(s.begin(), s.end(), a.begin(), [](auto b) { return std::byte(unsigned(b) << 1U); });
return a;
}
struct S {
explicit S(const std::stringamp; s) : arr_(get_arr(s)) {}
std::array<std::byte, 20> arr_;
};
int main(int /*argc*/, char* argv[]) {
// in reality we are reading about 600'000'000 strings from a file
S s(argv[1]);
return static_cast<int>(s.arr_[19]); // use it to avoid optimising away
}
Комментарии:
1. Я не думаю, что на этот вопрос есть однозначный ответ, если только кто-то из реализаций компилятора не сможет ответить. Профилировщик может определить, занимает ли алгоритм, который вы используете, больше всего времени при инициализации
arr
, или есть что-то еще, требующее времени (например, копирование).2. @Yksisarvinen Разве c 17 и последующие версии не гарантируют RVO во многих случаях? А как насчет таких агрегатов, как
std::array
? как насчет того, когда вызывающий код является «конструктором», инициализирующим элемент в списке инициализации конструктора?3. Я не вижу здесь ничего для РВО. Для количества элементов, которые вы храните, вы должны использовать std::deque
4. Действительно, C 17 предписывает, RVO, но это не RVO, это называется RVO — NRVO. Стандарт предписывает только копирование elision для RVO, не обязательно для NRVO. Насколько я знаю, новые правила в C 17 применяются в основном к временным, потому что точка материализации переменной может (и должна) быть перемещена туда, где на самом деле используется временное. В этом случае массив уже необходим
get_arr
, поэтому компилятору решать, сможет ли он следовать стеку вызовов и построить массив на месте или нет. Если ассамблея скажет вам, что произошло NRVO — что ж, в принципе, это все, что можно сказать.5. Примечание. Единственными различиями в сборке для аналогичного
void get_arr2(std::array<std::byte, 20>* arr_out, const std::stringamp; s);
определения являются одна дополнительнаяmov
инструкция, связанная с другим соглашением о вызовах, а также выбор различий, для которых используются регистры: godbolt.org/z/rrzhb5qba