С++, почему в контексте конструкторов перемещения и операторов присваивания перемещения требуется noexcept для включения оптимизации?

Рассмотрим следующий класс с конструктором перемещения и оператором присваивания перемещения:

class my_class
{

    protected:

    double *my_data;
    uint64_t my_data_length;
}

my_class(my_class&& other) noexcept : my_data_length{other.my_data_length}, my_data{other.my_data}
{
    // Steal the data
    other.my_data = nullptr;
    other.my_data_length = 0;
}

const my_class& operator=(my_class&& other) noexcept
{
    // Steal the data
    std::swap(my_data_length, other.my_data_length);
    std::swap(my_data, other.my_data);

    return *this;
}

Какова цель noexcept здесь? Я знаю, что компилятор обращается к тому, что следующая функция не должна создавать исключений: но как это позволяет оптимизировать компилятор?


person FreelanceConsultant    schedule 26.08.2015    source источник


Ответы (3)


Особая важность noexcept конструкторов перемещения и операторов присваивания подробно объясняется в https://vimeo.com/channels/ndc2014/97337253

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

Например, когда вы делаете push_back(t) для вектора, если вектор заполнен (size() == capacity()), тогда ему необходимо выделить новый блок памяти и скопировать все существующие элементы в новую память. Если копирование любого из элементов вызывает исключение, библиотека просто уничтожает все созданные ею элементы в новом хранилище и освобождает новую память, оставляя исходный вектор неизменным (таким образом, выполняется строгая гарантия безопасности исключений). Было бы быстрее переместить существующие элементы в новое хранилище, но если бы перемещение могло вызвать выброс, то любые уже перемещенные элементы уже были бы изменены, и выполнение строгой гарантии было бы невозможно, поэтому библиотека будет пытаться переместить их только тогда, когда он знает, что не может бросить, что он может знать, только если они noexcept.

person Jonathan Wakely    schedule 26.08.2015
comment
Почему он НЕ может выполнить лучшую оптимизацию? Насколько я понимаю, можно выполнить лучшее встраивание. Это неправильно и если да, то почему? - person Klaus; 26.08.2015
comment
@Клаус, для примеров ОП компилятор уже может видеть, что эти операции не вызывают исключения, добавление noexcept ничего не говорит. В общем, если определение функции видно и подходит для встраивания, то компилятор имеет всю информацию, необходимую для принятия такого решения в любом случае, без noexcept. Любые преимущества codegen (например, отсутствие необходимости генерировать информацию о раскручивании) относятся ко всем функциям, тогда как вопрос спрашивает, почему noexcept важен в контексте конструкторов перемещения и операторов присваивания. - person Jonathan Wakely; 26.08.2015
comment
Конечно, если в блоке перевода все видно, то все вполне идеально. Это также верно для const и final. Насколько я понимаю, если определения НЕ видны, оптимизация может быть выполнена лучше, если объявление дает некоторые подсказки, такие как noexcept, оптимизация становится немного лучше. Не так идеально, как с кодом в той же единице перевода, но лучше, так как нет ни noexcept, ни самого кода. - person Klaus; 26.08.2015
comment
Однако это все еще не относится к операциям перемещения, это применимо в равной степени ко всем функциям. - person Jonathan Wakely; 26.08.2015
comment
Тоже ясно! Специфичным для операций перемещения является только тот аспект, в котором STL использует эту информацию, имея специализации для используемого кода. Но мне также интересно, может ли наличие noexcept помочь сгенерировать лучший код, если определение не видно. - person Klaus; 26.08.2015

ИМХО, использование noexcept само по себе не включит оптимизацию компилятора. В STL есть трейты:

std::is_nothrow_move_constructible
std::is_nothrow_move_assignable

Контейнеры STL, такие как vector и т. д., используют эти черты для проверки типа T и используют конструкторы перемещения и присваивания вместо конструкторов копирования и присваивания.

Почему STL использует эти черты вместо:

std::is_move_constructible
std::is_move_assignable

Ответ: обеспечить сильную гарантию исключения.

person Elohim Meth    schedule 26.08.2015
comment
Ах, значит, для меня важно всегда помнить о добавлении noexcept к конструктору перемещения или оператору присваивания перемещения? - person FreelanceConsultant; 26.08.2015
comment
Да, это важно, если ваш конструктор копирования, например, выполняет глубокое копирование данных, и вы хотите использовать свой класс в контейнерах STL. - person Elohim Meth; 26.08.2015

Прежде всего, я хотел бы отметить, что в конструкторах перемещения или присваивании перемещения ничего не должно бросаться и, похоже, в этом нет необходимости. Единственное, что нужно сделать в конструкторах/операторах присваивания, это иметь дело с уже выделенной памятью и указателями на них. Обычно вы не должны вызывать какие-либо другие методы, которые могут вызывать броски, и ваше собственное перемещение внутри вашего конструктора/оператора не нуждается в этом. Но, с другой стороны, простой вывод отладочного сообщения нарушает это правило.

Оптимизация может быть выполнена различными способами. Автоматически компилятором, а также различными реализациями кода, использующими ваши конструкторы и оператор присваивания. Взгляните на STL, есть некоторые специализации для кода, которые отличаются, если вы используете исключения или нет, которые реализуются через признаки типа.

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

Тут тоже возник вопрос: noexcept, раскрутка стека и производительность

Может быть, ваш вопрос дублирует этот?

Возможно, полезный вопрос, связанный с этим, я нашел здесь: Являются ли конструкторы перемещения должно быть noexcept? Здесь обсуждается необходимость добавления операций перемещения.

Какова цель noexcept здесь?

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

person Klaus    schedule 26.08.2015
comment
Да, это не отвечает на мой вопрос... Из того, что вы говорите в первом абзаце, похоже, что noexcept просто используется компилятором для проверки того, может ли какая-либо часть функции генерировать исключение, которое, если оно истинно, вызовет компилятор ошибка? (Аналогично использованию const в конце объявления функции-члена? В этом случае мы говорим, что эта функция не будет изменять какие-либо данные-члены, и если это произойдет, мы получим ошибку компилятора.) Это правильная интерпретация вашего первый параграф? - person FreelanceConsultant; 26.08.2015
comment
Вы вообще не получите ошибки компиляции, но если во время выполнения произойдет бросок, ваша программа обычно завершается. Таким образом, у компилятора есть представление о том, как выглядит дерево вызовов. И если произойдет бросок, это не имеет значения, потому что выполнение этого метода никогда не продолжается, потому что программа завершается. Но мой первый абзац только спрашивает, почему кто-то должен выполнять операцию перемещения. Я ожидаю, что в этом нет никакой необходимости. - person Klaus; 26.08.2015
comment
Итак, в основном моя интерпретация неверна, и важно поставить там noexcept? - person FreelanceConsultant; 26.08.2015
comment
Я добавил свой ответ в надежде, что он станет немного яснее. Да, noexcept, как правило, приводит к лучшей генерации кода с меньшим потреблением места. - person Klaus; 26.08.2015
comment
@ user3728501, что, если true, вызовет ошибку компилятора? нет, как говорит Клаус, спецификации исключений не проверяются во время компиляции. - person Jonathan Wakely; 26.08.2015
comment
@JonathanWakely Спасибо за разъяснения. - person FreelanceConsultant; 26.08.2015