Оптимизация tmp переменной в функции

247
19 ноября 2017, 16:21

Подскажите, пожалуйста, какую оптимизацию проведет компилятор в таком коде:

void MyClass::clear() noexcept
{
    MyClass tmp;
    this->swap(tmp);
}

Или можно сразу написать так (сделает ли так компилятор?):

void MyClass::clear() noexcept
{
    this->swap(MyClass());
}

Компилятор MS Visual-C++. Во втором примере немного смущает то, что в функцию-метод класса swap, принимающую неконстантную ссылку, передается объект «без переменной». Как бы ничего от этого формально не страдает, но выглядит немного возбуждающе.

void MyClass::swap(MyClass& other) noexcept
{
    using std::swap;
    swap(this->m_number, other.m_number);
}
Answer 1

Ладно, вот код - простейший класс.

class Test {
public:
    Test()             {}
    Test(int x):val_(x){}
    Test(const Test& t):val_(t.val_) {}
    Test(Test&&t)      :val_(t.val_) {}
    Test& operator = (const Test& t)  {
        val_ = t.val_;
        return *this;}
    Test& operator = (Test&& t) {
        val_ = t.val_; t.val_ = 0;
        return *this;}
    ~Test()           {}
    int val() const { return val_; }
    void swap(Test& t) { ::swap(val_, t.val_); }
    void clear1() noexcept
    {
        Test tmp;
        this->swap(tmp);
    }
    void clear2() noexcept
    {
        this->swap(Test());
    }
private:
    int val_ = 0;
};
int main(int argc, const char * argv[])
{
    Test x(5), y(6);
    x.clear1();
    y.clear2();
}

Компилируется это чудо только с предупреждением (в общем-то, я предупреждал в комментариях)

test.cpp(37): warning C4239: нестандартное расширение: аргумент: преобразование "Test" в "Test &"
test.cpp(37): note: Неконстантная ссылка может быть связана только с левосторонним значением

Если использовать это "нестандартное расширение", то ассемблерный код VC++ 2017 идентичен при включенной оптимизации и идентичен с точностью до одной ассемблерной команды при выключенной - в одном месте clear1() использовано обращение к памяти, в то время как в clear2() - к регистру.

Так что пользуйтесь тем способом, который не вызывает предупреждений.

P.S. Впрочем, еще можно - если хотите - добавить перегрузку swap для rvalue.

Answer 2

Я собрал Ваш пример (минимальный) в gcc.godbolt.org. И решил посмотреть результат оптимизации. Он приятно удивил. Функция clear (первый вариант) стала такой

MyClass::clear():
  mov DWORD PTR [rdi], 0
  ret

То есть, компилятор выбросил создание новой переменной и вызов swap. А просто занулил переменную. Это делает gcc c 4.9.4 и новее даже на уровне оптимизации O1 (более младшие нет смысла проверять). Clang похоже такое умеет делать с 3.7. Как по мне - компилятор управился просто на отлично.

Второй вариант clear не компилируется gcc/clang - поэтому, проверить результат компиляции сложно.

2017 студия делает такое же. Но при этом она компилирует оба варианта функции clear.

Вывод. Пишите код просто и явно. Компилятор сам все сделает. И сделает явно не хуже ручной оптимизации.

А также может прийти доброжелатель, который решит добавить ключик '/Za', который отключает подобные "особенности компилятора студии" и все... код не будет собираться.

READ ALSO
2 оператора присваивания или один?

2 оператора присваивания или один?

Итак имеется два оператора присваивания - копирующий и перемещающий, например такие:

201
Какие функции swap нужны для класса

Какие функции swap нужны для класса

В данный момент имеется функция-член класса swap:

239
Расширение строки QListView

Расширение строки QListView

У меня есть QListView и модель для него, унаследованная от QAbstractListModelМне необходимо отображать элементы модели при помощи кастомных виджетов

271
Как получить из лямбды значение, которое она вернёт?

Как получить из лямбды значение, которое она вернёт?

Я бы хотел таким образом получить максимальный элемент field, но не понимаю, как получить возвращаемое значение

257