Предотвращает ли возвращение созданного по умолчанию объекта NRVO?

Предположим, у меня есть такая функция:

using std::vector;

vector<int> build_vector(int n)
{
   if (some_condition(n)) return {};

   vector<int> out;

   for(int x : something())
   {
      out.push_back(x);
   }

   return out;
}

Ли return {} в начале функции предотвратить NRVO? Мне любопытно, так как кажется, что это будет эквивалентно следующему:

using std::vector;

vector<int> nrvo_friendly_build_vector(int n)
{
   vector<int> out;

   if (some_condition(n)) return out;

   for(int x : something())
   {
      out.push_back(x);
   }

   return out;
}

Но мне не было ясно, разрешено ли компилятору выполнять NRVO в первом случае.

2 ответа

Решение

Gordon Bailey ФП, здесь приведена адаптированная версия моего комментария.

На самом деле мне было интересно то же самое (особенно потому, что в стандарте не требуется "elision copy"), поэтому я быстро протестировал его в онлайн-компиляторе, заменив std::vector с помощью структуры виджета:

struct Widget
{
    int val = 0;
    Widget()              { printf("default ctor\n"); }
    Widget(const Widget&) { printf("copy ctor\n"); }
    Widget(Widget&&)      { printf("move ctor\n"); }

    Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
    Widget& operator=(Widget&&)      { printf("move assign\n"); return *this; }

    ~Widget() { printf("dtor\n"); }

    void method(int)
    {
        printf("method\n");
    }
};

V1 используя build_vector(): http://coliru.stacked-crooked.com/a/5e55efe46bfe32f5

#include <cstdio>
#include <array>
#include <cstdlib>

using std::array;

struct Widget
{
    int val = 0;
    Widget()              { printf("default ctor\n"); }
    Widget(const Widget&) { printf("copy ctor\n"); }
    Widget(Widget&&)      { printf("move ctor\n"); }

    Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
    Widget& operator=(Widget&&)      { printf("move assign\n"); return *this; }

    ~Widget() { printf("dtor\n"); }

    void method(int)
    {
        printf("method\n");
    }
};

bool some_condition(int x)
{
    return (x % 2) == 0;
}

array<int, 3> something()
{
    return {{1,2,3}};
}

Widget build_vector(int n)
{
   if (some_condition(n)) return {};

   Widget out;

   for(int x : something())
   {
      out.method(x);
   }

   return out;
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        return -1;
    }
    const int x = atoi(argv[1]);

    printf("call build_vector\n");
    Widget w = build_vector(x);
    printf("end of call\n");
    return w.val;
}

Выход V1

call build_vector
default ctor
method
method
method
move ctor
dtor
end of call
dtor

V2 с помощью nrvo_friendly_build_vector(): /questions/39784826/proverit-nalichie-izmenenij-v-tablitse-sql-server/39784847#39784847

#include <cstdio>
#include <array>
#include <cstdlib>

using std::array;

struct Widget
{
    int val = 0;
    Widget()              { printf("default ctor\n"); }
    Widget(const Widget&) { printf("copy ctor\n"); }
    Widget(Widget&&)      { printf("move ctor\n"); }

    Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
    Widget& operator=(Widget&&)      { printf("move assign\n"); return *this; }

    ~Widget() { printf("dtor\n"); }

    void method(int)
    {
        printf("method\n");
    }
};

bool some_condition(int x)
{
    return (x % 2) == 0;
}

array<int, 3> something()
{
    return {{1,2,3}};
}

Widget nrvo_friendly_build_vector(int n)
{
   Widget out;

   if (some_condition(n)) return out;

   for(int x : something())
   {
      out.method(x);
   }

   return out;
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        return -1;
    }
    const int x = atoi(argv[1]);

    printf("call nrvo_friendly_build_vector\n");
    Widget w = nrvo_friendly_build_vector(x);
    printf("end of call\n");
    return w.val;
}

Выход V2

call nrvo_friendly_build_vector
default ctor
method
method
method
end of call
dtor

Как вы можете видеть, в этом конкретном случае (никакие побочные эффекты от создания структуры не видны по some_condition), V1 вызывает конструктор перемещения, если some_condition() ложно (по крайней мере, в Clang и GCC, используя -std=c++11 а также -O2 в колиру)

Кроме того, как Gordon Bailey, такое же поведение, кажется, происходит в -O3 также.

НТН

PS: Когда вы узнаете о возможностях копирования, вам может понравиться Abseil ToW #11;)

С https://en.cppreference.com/w/cpp/language/copy_elision

При следующих обстоятельствах компиляторам разрешено, но не обязательно пропускать конструкцию объектов класса copy и move (начиная с C++11), даже если конструктор copy/move (начиная с C++11) и деструктор имеют видимую сторону -последствия. Объекты создаются непосредственно в хранилище, где они в противном случае были бы скопированы / перемещены. Это оптимизация: даже когда это имеет место и конструктор копирования / перемещения (начиная с C++11) не вызывается, он все равно должен присутствовать и быть доступным (как если бы оптимизация вообще не происходила), иначе программа не работает. сформировавшиеся:

  • В операторе возврата, когда операндом является имя энергонезависимого объекта с автоматической продолжительностью хранения, который не является параметром функции или параметром предложения catch и имеет тот же тип класса (игнорирующий квалификацию cv), что и тип возврата функции. Этот вариант исключения копии известен как NRVO, "оптимизация именованного значения".

  • ...

С досрочным возвратом ограничений нет, поэтому обе версии являются кандидатами на NRVO.

Другие вопросы по тегам