1

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

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);
}
Jens
  • 3,373
  • Как минимум компилятор обругает MyClass tmp();, приняв его за объявление функции, так что используйте второй вариант :) – Harry Nov 17 '17 at 07:36
  • @Harry там взов к-ра по умолчанию для создания нулевого объекта, поправил – Jens Nov 17 '17 at 07:37
  • Тогда расскажите, как объявлен swap так, что у вас проходит второй вариант - как swap(MyClass&&)? Если так - то первый вариант не сработает, если как swap(MyClass&) - то не сработает второй... – Harry Nov 17 '17 at 07:41
  • Ну вот вы сами откомпилируйте и посмотрите. https://godbolt.org/ – VladD Nov 17 '17 at 07:45
  • @Harry swap стандартно объявлена, добавил – Jens Nov 17 '17 at 07:48
  • это vs, она позволяет rvalue передавать, несмотря на нарушение стандарта. – KoVadim Nov 17 '17 at 07:49
  • @KoVadim да VS, т.е. для переносимости tmp создавать? – Jens Nov 17 '17 at 07:54
  • почему "для переносимости"? пишите код правильно - это нормально.А писать, основываясь на особенностях реализации - это плохо. Также плохо делать оптимизацию там, где она не нужна - компиляторы сейчас умные, сами умеют. А если сильно-сильно нужно clear оптимизировать (профайлер подсказал), тогда уже явно инициализируйте или memset'ом (если конечно понимаете, что делаете) – KoVadim Nov 17 '17 at 07:59

2 Answers2

4

Я собрал Ваш пример (минимальный) в 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', который отключает подобные "особенности компилятора студии" и все... код не будет собираться.

KoVadim
  • 112,121
  • 6
  • 94
  • 160
2

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

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.

Harry
  • 221,325
  • Просто интересно - в последнее время появился кто-то, кто просто автоматом лепит везде (не только мне) минусы, при этом не утруждая себя хотя бы возражениями по существу. Коллега, не стесняйтесь, выскажите свое мнение - ведь раз вы так уверенно поступаете, у вас просто не может быть своего мнения? А то получается, как в той поговорке - "не знаю, как, но знаю, что не так" :) – Harry Nov 17 '17 at 08:19
  • Больше на бота похоже, не может же минусолепитель в каждой теме сидеть? – ixSci Nov 17 '17 at 08:27
  • А репутация у бота тогда откуда берется? Минусы за бесплатно не ставятся – Jens Nov 17 '17 at 08:27
  • @ixSci Кто его знает... Забавно глянуть, например, на этот вопрос - как старательно каждому ответу влеплено по два минуса :) Есть один ответ с 3 минусами - но там на один минус имеется комментарий... – Harry Nov 17 '17 at 08:29