Зачем реализовывать swap() как не бросающий

Я понимаю, почему иногда рекомендуется реализовать собственную функцию swap() для данного класса.

Например, если у нас есть класс, использующий идиому pimpl, мы, вероятно, захотим определить собственный конструктор копирования, чтобы он выполнял глубокое копирование содержимого объекта, переданного в качестве аргумента, вместо поверхностного копирования, которое было бы выполнено конструктором копирования по умолчанию. То же самое можно применить к оператору присваивания копии.

Поскольку кажется, что std::swap() реализован с точки зрения (по крайней мере, когда речь идет о C++03) как конструктора копирования, так и оператора присваивания копирования. Было бы неэффективно выполнять глубокие копии объектов, подлежащих замене, поскольку необходимо выполнить только замену указателей, содержащихся в этих объектах.

Мой вопрос заключается в том, почему мы должны реализовать нашу функцию swap() как не генерирующую.

В случае, описанном выше, я предполагаю, что речь идет только о семантике: поскольку новые ресурсы не выделяются (т. Е. Два существующих указателя просто меняются местами). Для такой функции не имеет особого смысла генерировать исключение.

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


person 眠りネロク    schedule 18.05.2017    source источник
comment
Встречный вопрос: зачем нам реализовывать функцию броска, если в ней нет необходимости? Исключения должны быть обработаны. Вы должны решить, что делать, и защитить код с помощью try-catch всякий раз, когда вы вызываете эту функцию. Это немного работы. Если есть способ полностью избежать этого, то почему бы и нет?   -  person Fabio says Reinstate Monica    schedule 18.05.2017
comment
1. Что вы подразумеваете под не бросанием? Вы имеете в виду throw()? 2. Прежде чем задать вопрос почему swap не должен вызывать выбросы, как насчет того, чтобы сначала спросить: должен swap не вызывать выбросы?   -  person eerorika    schedule 18.05.2017
comment
Когда бы поменяться местами?   -  person Caleth    schedule 18.05.2017
comment
На самом деле я имею в виду, что функция никогда не выдает исключение. Не throw() как часть сигнатуры функции.   -  person 眠りネロク    schedule 18.05.2017


Ответы (4)


Для некоторых классов, таких как

класс, следующий за идиомой pimpl

мы знаем, что реализацию swap не нужно будет бросать, потому что

Для такой функции не имеет особого смысла генерировать исключение.

Когда нет смысла генерировать исключение, лучше не генерировать исключение.


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

person eerorika    schedule 18.05.2017
comment
Таким образом, реализация нашей собственной функции swap() связана не только с производительностью, но и с безопасностью исключений, т. е. с переходом от функции swap(), которая может генерировать ошибки, к функции, которая этого не делает. Неспециализированная версия std::swap() может выдать ошибку, поскольку могут быть выделены ресурсы. - person 眠りネロク; 18.05.2017
comment
@Neroku очень острое наблюдение. - person eerorika; 18.05.2017
comment
Неспециализированная версия swap() вряд ли выкинет теперь, она использует конструкцию перемещения и присваивание перемещения, если они существуют. - person Useless; 18.05.2017
comment
@Useless сейчас означает с C++11, если быть точным. - person eerorika; 18.05.2017

Мой вопрос заключается в том, почему мы должны реализовать нашу функцию swap() как не бросающую

  1. Потому что swap совершенно бесполезен, если может кинуть.

    Учтите: у вас swap два экземпляра, и операция выбрасывает. Теперь, в каком они состоянии?

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

    Если мы не можем выполнить строгую гарантию, мы просто не сможем использовать swap во многих случаях, потому что нет никакого способа восстановиться после сбоя, и нет смысла писать эту версию swap в все.

  2. Потому что нет причин его бросать.

    Тривиальная реализация swap (сейчас) использует присваивание перемещения и -конструкцию.

    Как правило, у конструктора перемещения нет причин выбрасывать: он не выделяет ничего нового, он просто переустанавливает существующие данные. Как правило, нет причин для броска присваивания перемещения (как указано выше), и деструкторы никогда не должны генерировать - и это единственные требуемые операции.

person Useless    schedule 18.05.2017
comment
И, следуя первому пункту, если тип A имеет потенциально генерирующий своп, это усложняет композицию. Если тип B имеет два члена A, то B::swap(B&) нельзя безопасно поменять местами, потому что любой A может бросить и оставить B в неизвестном и невосстановимом состоянии. - person Jonathan Wakely; 18.05.2017
comment
По поводу первого пункта. Что, если конструктор копирования может генерировать, но не оператор присваивания копирования? В этом случае swap() может бросить, но сильная гарантия не теряется. - person 眠りネロク; 18.05.2017
comment
Неправда - вы могли бы успешно завершить обмен первым из двух членов A B, а затем добавить второго. В этом случае ваши объекты B находятся в потенциально непригодном для использования состоянии. Вы все равно должны использовать варианты move сейчас, и редко есть веская причина для их броска. - person Useless; 18.05.2017

ИЗМЕНИТЬ

Первоначально я понял вопрос как «зачем использовать спецификатор throw для функции swap». Этот ответ может быть не по теме, поскольку я не объясняю, почему swap никогда не выбрасывает.


Я думаю, что лучший ответ: почему бы и нет?.

Почему бы вам не указать, что функция никогда не будет бросать вызов, если эта функция не является причиной для выброса?

Вы всегда должны реализовывать функцию как не бросающую, когда у них нет причин генерировать исключение: вы предлагаете более сильную гарантию для своей функции.

Кроме того, с некоторым метапрограммированием вы можете воспользоваться преимуществами функций, не являющихся бросками. Некоторые классы STL используют это, чтобы иметь более быструю функцию-член, когда swap/copy/move (C++11) не является броском. (На самом деле я не уверен, что они используют функцию, не являющуюся броском в коде до С++ 11)

person nefas    schedule 18.05.2017

swapработа с pImpls не может потерпеть неудачу в правильно построенной программе . (И поведение в плохо сформированной программе не имеет значения). Нечего бросать

person Caleth    schedule 18.05.2017