NRVO std::массив в списке инициализаторов конструктора

#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