Оптимизация компилятора

Поэтому у меня есть вопрос для вас.:) Можете ли вы рассказать мне о выходе следующего кода?

#include <iostream>
struct Optimized
{
    Optimized() { std::cout << "ctor" << std::endl; }
    ~Optimized() { std::cout << "dtor" << std::endl; }
    Optimized(const Optimized& copy) { std::cout << "copy ctor" << std::endl; }
    Optimized(Optimized&& move) { std::cout << "move ctor" << std::endl; }
    const Optimized& operator=(const Optimized& rhs) { std::cout << "assignment operator" << std::endl; return *this; }
    Optimized& operator=(Optimized&& lhs) { std::cout << "move assignment operator" << std::endl; return *this; }
};

Optimized TestFunction()
{
    Optimized a;
    Optimized b = a;
    return b;
}

int main(int argc, char* argv[])
{
    Optimized test = TestFunction();
    return 0;
}

Мой первый ответ:

  • т е р
  • copy ctor
  • move ctor
  • dtor
  • dtor
  • dtor

и это правда, но , только если оптимизация компилятора отключена. Когда оптимизация включена, вывод полностью отличается. При включенной оптимизации выход:

  • т е р
  • copy ctor
  • dtor
  • dtor

При оптимизации компилятора тестовая переменная является возвращаемой переменной.

Мой вопрос: какие условия могут привести к тому, что это не будет оптимизировано таким образом?

Мне всегда учили, что возвращение структуры/класса, которая приводит к созданию дополнительных конструкторов копий, может быть лучше оптимизировано путем передачи в качестве ссылки, но компилятор делает это для меня. Так возвращается структура, которая все еще считается плохой формой?

Ответ 1

Это называется Copy Elision и является специальной обработкой вместо копирования/перемещения.

Оптимизация специально разрешена Стандартом, если можно было бы копировать/перемещать (т.е. метод объявлен и доступен).

Реализация в компиляторе обычно упоминается в этом случае как Оптимизация возвращаемого значения. Существует два варианта:

  • RVO: при возврате временного (return "aa" + someString;)
  • NRVO: N для имени, когда вы возвращаете объект с именем

Оба реализуются основными компиляторами, но последние могут работать только на более высоких уровнях оптимизации, поскольку их труднее обнаружить.

Поэтому, чтобы ответить на вопрос о возвращении структур: я бы порекомендовал его. Рассмотрим:

// Bad
Foo foo;
bar(foo);

-- foo can be modified here


// Good
Foo const foo = bar();

Последнее не только более ясное, но и позволяет const принудительное выполнение!

Ответ 2

Оба выхода допустимы. Стандарт языка С++ 03 говорит в разделе 12.8/15:

Когда выполняются определенные критерии, реализации разрешается опускать конструкцию копирования объекта класса, даже если конструктор копирования и/или деструктор для объекта имеют побочные эффекты. В таких случаях реализация рассматривает источник и цель опущенной операции копирования как просто два разных способа обращения к тот же объект и уничтожение этого объекта происходит в более поздние времена, когда два объекта было бы уничтожено без оптимизации. 111) Это разрешение операций копирования разрешено в следующие обстоятельства (которые могут быть объединены для устранения нескольких копий):

  • в выражении return в функции с возвращаемым типом класса, когда выражение является именем энергонезависимый автоматический объект с тем же cv-неквалифицированным типом, что и возвращаемый тип функции, копия операция может быть опущена путем создания автоматического объекта непосредственно в возвращаемое значение функций
  • когда временный объект класса, который не был привязан к ссылке (12.2), будет скопирован в класс объект с тем же CV-неквалифицированным типом, операция копирования может быть опущена путем создания временного объект непосредственно в цель пропущенной копии

Ответ 3

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

Будет ли это происходить или нет, может зависеть от возможных факторов, включая настройки оптимизации компилятора.

По моему мнению, вызывать вышеупомянутое копирование "оптимизация" не совсем корректно (хотя желание использовать этот термин здесь вполне понятно и широко используется для этой цели). Я бы сказал, что термин оптимизация должна быть зарезервирована для ситуаций, когда компилятор отклоняется от поведения абстрактной машины С++, сохраняя наблюдаемое поведение программы. Иными словами, настоящая оптимизация подразумевает нарушение абстрактных требований спецификации языка. Так как в этом случае нет нарушения (разрешение копии явно разрешено стандартом), нет реальной "оптимизации". Здесь мы наблюдаем только то, как язык С++ работает на абстрактном уровне. Не нужно вообще включать концепцию "оптимизации".

Ответ 4

Даже при переходе по значению компилятор может оптимизировать дополнительную копию, используя Оптимизацию возвращаемого значения; http://en.wikipedia.org/wiki/Return_value_optimization