Почему C ++ 0x `noexcept` проверяется динамически? - PullRequest
27 голосов
/ 04 мая 2010

Мне любопытно обоснование noexcept в C ++ 0x FCD . throw(X) устарел, но noexcept похоже делает то же самое. Есть ли причина, по которой noexcept не проверяется во время компиляции? Похоже, что было бы лучше, если бы эти функции были проверены статически, чтобы они вызывали только бросающие функции внутри блока try.

Ответы [ 6 ]

25 голосов
/ 15 апреля 2011

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

Подумайте, как это получится. Предположим, что требования были

  1. каждый деструктор неявно noexcept(true)
    • Возможно, это должно быть строгое требование. Метательные деструкторы всегда ошибка.
  2. каждый внешний "C" неявно noexcept(true)
    • Снова тот же аргумент: исключения в C-land всегда ошибка.
  3. каждая другая функция неявно noexcept(false), если не указано иное
  4. a noexcept(true) функция должна обернуть все свои noexcept(false) вызовы в try{}catch(...){}
    • По аналогии, метод const не может вызывать неконстантный метод.
  5. Этот атрибут должен проявляться как отдельный тип в разрешении перегрузки, совместимости указателя функции и т. Д.

Звучит разумно, верно?

Для реализации этого компоновщик должен различать версии функций noexcept(true) и noexcept(false), так как вы можете перегружать const и const версии функций-членов.

Так, что это значит для именования? Для обеспечения обратной совместимости с существующим объектным кодом нам потребуется, чтобы все существующие имена интерпретировались как noexcept(false) с дополнительным искажением для noexcept(true) версий.

Это будет означать, что мы не сможем связываться с существующими деструкторами, если заголовок не будет изменен, чтобы пометить их как noexcept(false)

  • это нарушит обратную совместимость,
  • это возможно должно быть невозможно, см. Пункт 1.

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

Как я уже сказал, я бы сломал ABI, предпочитая ломать язык. noexcept - это лишь незначительное улучшение по старому. Статическая проверка всегда лучше.

25 голосов
/ 04 мая 2010

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

С другой стороны, компилятор может оптимизировать код, который не генерирует исключения. См. « Дебаты по noexcept, часть I » (вместе с частями II и III ) для подробного обсуждения Кажется, главное:

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

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

    int func(); //might throw any exception
    
  • Функция, которая никогда не выбрасывает. На такую ​​функцию может указывать спецификация throw ():

    int add(int, int) throw(); //should never throw any exception
    
8 голосов
/ 04 мая 2010

Обратите внимание, что noexcept проверяет исключение, генерируемое ошибочными dynamic_cast и typeid, примененными к указателю null, что может быть сделано только во время выполнения. Другие тесты действительно могут быть выполнены во время компиляции.

5 голосов
/ 04 мая 2010

Как уже говорилось в других ответах, операторы типа dynamic_cast s могут выдавать, но могут быть проверены только во время выполнения, поэтому компилятор не может сказать наверняка во время компиляции.

Это означает, что во время компиляции компилятор может либо отпустить их (т. Е. Не проверять во время компиляции), либо предупредить, либо полностью отклонить (что было бы бесполезно). Это оставляет предупреждение как единственную разумную вещь для компилятора.

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

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

Так что компилятор не может применить это во время компиляции. Это больше относится к области статического анализа, который, как правило, более требователен и выдает предупреждения для такого рода вещей.

3 голосов
/ 04 мая 2010

Есть некоторые совпадения в функциональности noexcept и throw(), но они действительно идут с противоположных направлений.

throw() был о правильности, и ничего более: способ указать поведение, если было сгенерировано непредвиденное исключение.

Основная цель noexcept - это оптимизация: она позволяет / поощряет оптимизацию компилятора в предположении, что исключений не будет.

Но да, на практике, они недалеко от двух разных имен для одной и той же вещи. Мотивы, стоящие за ними, разные.

3 голосов
/ 04 мая 2010

Рассмотрим функцию

void fn() noexcept
{
   foo();
   bar();
}

Можете ли вы статически проверить, правильно ли это? Вы должны были бы знать, собираются ли foo или bar генерировать исключения. Вы можете заставить все вызовы функций находиться внутри блока try {}, что-то вроде этого

void fun() noexcept
{
    try
    {
         foo();
         bar();
    }
    catch(ExceptionType &)
    {
    }
}

Но это не так. Мы не можем знать, что foo и bar будут генерировать только исключения такого типа. Чтобы убедиться, что мы поймаем все, что нам нужно использовать «...». Если вы что-нибудь поймаете, что вы будете делать с любыми ошибками? Если здесь возникает непредвиденная ошибка, единственное, что нужно сделать, это прервать программу. Но это по сути то, что будет делать проверка времени выполнения, предоставленная по умолчанию.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...