Каковы побочные эффекты пометки производной реализации noexcept, если интерфейс не

У нас есть класс, который реализует IUnknown (или любой интерфейс, которым мы не владеем). Мы начали помечать большинство/все наши методы словом noexcept для любой потенциальной оптимизации, так как мы все равно не выбрасываем никаких исключений; хотя некоторые из библиотек, от которых мы зависим, могут. Был поднят вопрос о том, должен ли QueryInterface/AddRef/Release быть помечен как noexcept, поскольку интерфейс не помечен.

Есть ли какие-либо побочные эффекты или ошибки, когда только некоторые из производных классов помечены как noexcept?


person AdIch    schedule 22.10.2018    source источник
comment
Помните, что noexcept не является бесплатным. Не лучшая идея набрасывать на него все функции. Цена в том, что вы не можете снять его после того, как надели. Другой noexcept код может полагаться на noexcept вашей функции, поэтому для обратной совместимости вы застряли. Это означает, что вы никогда не сможете изменить реализацию на что-то, что может выдать ошибку. Добавление noexcept — это улица с односторонним движением, безопасно добавлять (если это правильно), но небезопасно удалять. Вы должны быть уверены, что функция по своей сути никогда не может бросить вызов.   -  person François Andrieux    schedule 23.10.2018


Ответы (2)


Вы должны быть осторожны с noexcept в целом. Если компилятор не может доказать, что функция действительно не будет генерировать никаких исключений, он должен вставить динамический обработчик для завершения вашей программы в случае исключения. Таким образом, это не обязательно приведет к оптимизации, на которую вы надеетесь. В любом случае, добавление его к AddRef, Release и QueryInterface должно быть безопасным.

Изменить

Например, рассмотрим следующий код:

extern int Foo();

int Bar() noexcept
{
    return Foo();
}

Вот что Clang 7.0 генерирует на O3:

Bar():                                # @Bar()
    push    rax
    call    Foo()
    pop     rcx
    ret
    mov     rdi, rax
    call    __clang_call_terminate
__clang_call_terminate:                 # @__clang_call_terminate
    push    rax
    call    __cxa_begin_catch
    call    std::terminate()

Если вы удалите noexcept, вместо этого вы получите это:

Bar():                                # @Bar()
    jmp     Foo()                 # TAILCALL

В этом примере основным эффектом является просто небольшое увеличение изображения, но обратите внимание, что вызов Foo также стал немного менее эффективным.

person Peter Ruderman    schedule 22.10.2018
comment
Что вы подразумеваете под вставкой динамического обработчика? Как это влияет на производительность? - person Passer By; 23.10.2018
comment
Это небольшой фрагмент кода для перехвата исключений и вызова std::terminate. Это редко проблема. - person Peter Ruderman; 23.10.2018

Я всегда рекомендую добавлять noexcept везде, где это возможно.

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

person Luke Kelly    schedule 22.10.2018
comment
RCA эскалации: инженеры узнали, что они не могут использовать noexcept для этой критически важной для бизнеса функции. - person Mac; 23.10.2018