Visual Studio не выполняет RVO, когда используется троичный оператор и удаляются / перемещаются векторы - PullRequest
5 голосов
/ 14 марта 2019

Глядя на приведенный ниже пример кода, я ожидал бы, что он выполнит обязательное копирование в рамках оптимизации возвращаемых значений (RVO) и скомпилирует с C ++ 17 (/ std: c ++ 17), но он компилируется с ошибкой на Visual Studio 2017 (я использую VS17, более конкретно 15.9.8).

class NoCopyOrMove
{
public:
    NoCopyOrMove() = default;
    NoCopyOrMove(int a, int b){}

    NoCopyOrMove(const NoCopyOrMove&) = delete;
    NoCopyOrMove& operator=(const NoCopyOrMove&) = delete;

    NoCopyOrMove(NoCopyOrMove&&) = delete;
    NoCopyOrMove& operator=(NoCopyOrMove&&) = delete;


private:
    int a, b;
};

NoCopyOrMove get(bool b) 
{
    return b ? NoCopyOrMove(1,2) : NoCopyOrMove();

    //if (b)
    //    return NoCopyOrMove(1, 2);

    //return NoCopyOrMove();
}

int main()
{
    NoCopyOrMove m = get(true);
}

Ошибка:

error C2280: 'NoCopyOrMove::NoCopyOrMove(NoCopyOrMove &&)': attempting to reference a deleted function
note: see declaration of 'NoCopyOrMove::NoCopyOrMove'
note: 'NoCopyOrMove::NoCopyOrMove(NoCopyOrMove &&)': function was explicitly deleted

ПРИМЕЧАНИЕ: кажется, что компилируется на GCC, а версия с if / else прекрасно компилируется на обоих, поэтому не уверен, что мне не хватает.

Я нашел несколько других вопросов по stackoverflow, но они были из эпохи до c17 и в основном имели в виду «копия вызывается вместо перемещения», следовательно, спрашиваю снова.

основано на cppreference. Удаление копии происходит:

В операторе возврата, когда операнд является prvalue того же класса type (игнорируя cv-qualification) как тип возвращаемого функцией:

и результат троичного оператора должен быть равен prvalue:

а? b: c - троичное условное выражение для некоторых b и c (см. определение для деталей);

Есть идеи, почему он не компилируется?


Изменить, чтобы использовать более простой код:

с учетом NoCopyOrMove выше, код ниже также пытается вызвать move-ctor.

int main()
{
    volatile bool b = true;
    NoCopyOrMove m = b ? NoCopyOrMove(1,2) : NoCopyOrMove();
}

Обновление: ссылка на отчет

1 Ответ

5 голосов
/ 15 марта 2019

Это ошибка?

Да.Это ошибка в MSVC. Практически любой компилятор, поддерживающий C ++ 17, компилирует его.Ниже мы имеем сборку, произведенную:

И все они компилируют его с -std=c++17 или -std=c++1z (для ellcc).

Что говорится в стандарте?

Условные выражения (сформированные тернарным оператором) создают значения в соответствии с этими правилами (см. Раздел 8.5.16).

В параграфе 1 раздела 8.5.16 описывается последовательность, а в частях 2–7 описывается категория значений результирующего выражения (описание категорий значений см. В разделе 8.2.1).

  • В пункте 2 рассматривается случай, когда второй или третий операнды являются недействительными.
  • В пункте 3 рассматривается случай, когда и второй, и третий операнды являются битовыми полями с оценкой (т. Е. Не являются prvalues))
  • Пункт 4 охватывает случай, когда второй и третий операнды имеют разные типы
  • Пункт 5 охватывает случай, когда второй и третий операнды являются glvalues ​​одного и того же типа (также не prvalues)
  • Пункт 6:

В противном случае результатом является значение prvalue.Если второй и третий операнды не имеют один и тот же тип и оба имеют (возможно, cv-квалифицированный) тип класса, разрешение перегрузки используется для определения преобразований (если они есть), которые должны применяться к операндам (16.3.1.2, 16.6),Если не удается разрешить перегрузку, программа работает некорректно.В противном случае применяются определенные таким образом преобразования, а преобразованные операнды используются вместо исходных операндов в оставшейся части этого подпункта.

Это дает нам наш ответ.Результатом является значение prvalue, поэтому нет необходимости использовать конструкторы копирования или перемещения, так как значение будет создано в памяти, предоставленной вызывающей функцией (это местоположение в памяти передается как «скрытый» параметр вашей функции).

Ваша программа неявно ссылается на конструктор перемещения или конструктор копирования?

Джон Харпер был достаточно любезен, чтобы указать, что стандарт заявляет:

Программакоторая ссылается на удаленную функцию неявно или явно, кроме как для ее объявления, является неправильно сформированной.(11.4.3.2)

Возникает вопрос: неявно ли ваша программа ссылается на конструктор перемещения или конструктор копирования?

Ответ на этот вопрос - нет. Поскольку результатом условного выражения является значение prvalue, временные данные не материализуются, и в результате ни на конструктор перемещения, ни на конструктор копирования не ссылаются ни явно, ни неявно.Процитируем cppreference (выделение мое):

При следующих обстоятельствах компиляторы обязаны опустить копирование и переместить конструкцию объектов класса, даже если конструктор копирования / перемещенияи деструктор имеет наблюдаемые побочные эффекты.Объекты создаются непосредственно в хранилище, где они в противном случае были бы скопированы / перемещены. Конструкторы копирования / перемещения не обязательно должны присутствовать или быть доступными , поскольку языковые правила гарантируют, что операция копирования / перемещения не выполняется, даже концептуально:

  • В возвратеоператор, когда операнд является prvalue того же типа класса (игнорируя квалификацию cv), что и тип возврата функции:

    T f() { return T(); }

    f(); // only one call to default constructor of T

  • При инициализации переменной, когда выражение инициализатора является prvalue того же типа класса (игнорируя квалификацию cv), что и тип переменной:

T x = T(T(f())); // only one call to default constructor of T, to initialize x

Различение между NRVO и RVO

Одним из источников разногласий является вопрос о том, гарантируется ли Copy Elision.Важно различать оптимизацию именованных возвращаемых значений и чистую оптимизацию возвращаемых значений.

Если вы возвращаете локальную переменную, это не гарантируется.Это именованная оптимизация возвращаемого значения.Если ваш оператор возврата является выражением, которое является prvalue, оно гарантировано.

Например:

NoCopyOrMove foo() {
    NoCopyOrMove myVar{}; //Initialize
    return myVar; //Error: Move constructor deleted
}

Я возвращаю выражение (myVar), которое является именемобъект автоматического хранения.В этом случае оптимизация возвращаемого значения разрешена, но не гарантируется.Раздел 15.8.3 стандарта применяется здесь.

С другой стороны, если я напишу:

NoCopyOrMove foo() {
    return NoCopyOrMove(); // No error (C++17 and above)
}

Копирование Elision гарантировано , и копирование или перемещение не выполняются.Точно так же, если я напишу:

NoCopyOrMove foo(); //declare foo
NoCopyOrMove bar() {
    return foo(); //Returns what foo returns
}

Копировать Elision все еще гарантировано , потому что результат foo() является prvalue.

Заключение

MSVC действительно имеет ошибку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...