Подтвердить что ты не робот

С++ noexcept для функции, которая не бросает исключения, но может вызвать сбой памяти

Например, довольно распространено иметь два отдельных способа доступа к элементам частного массива, перегрузку оператора подписи текста массива или определение at:

T& operator[](size_t i) { return v[i]; }
T const& operator[](size_t i) const { return v[i]; }

T& at(size_t i)
{
     if (i >= length)
         throw out_of_range("You shall not pass!");

     return v[i];
}

T const& at(size_t i) const
{
     if (i >= length)
         throw out_of_range("You shall not pass!");

     return v[i];
}

Версия at может генерировать исключение, но оператор подписи текста массива не может.

Мой вопрос в том, что если operator[] не генерирует исключение, может ли он быть помечен как noexcept, даже зная, что он может поднять сигнал SIGSEGV или это только плохая практика?

Я хочу указать, что сигнал (как SIGSEGV) не является исключением. Буквально интерпретируя смысл noexcept, функция noexcept - это та, которая утверждает, что не будет генерировать исключения. Он ничего не говорит ни о чем другом (включая сигналы).

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

Итак, нецелесообразно ли отмечать noexcept функцию, которая небезопасна?

4b9b3361

Ответ 1

Это сложный вопрос и поднимает некоторые ключевые проблемы, рассмотренные в noexcept - зачем? из Andrzej С++ blog, в котором говорится, что я попытаюсь привести здесь минимум (основное внимание):

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

и

Учитывая это негативное отношение к noexcept, его можно считать полезным вообще? Да. Функция noexcept была введена очень поздно в С++ 11 для решения одной конкретной проблемы с семантикой перемещения. Это описано здесь Дугласом Грегором и Дэвидом Абрахамом.

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

[...] Это потому, что информация, которую noexcept действительно предназначена для что функция никогда не терпит неудачу; не то, что он никогда не бросает! Мы может видеть выше, что функция может терпеть неудачу, но все же не бросать, но она по-прежнему квалифицируется как noexcept (false). Возможно, ключевое слово должно иметь был назван nofail. Гарантия никогда не может быть проверена компилятор (как и любая другая гарантия безопасности при отказе), поэтому Единственное, что мы можем сделать, это объявить его.

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

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

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

Он также отмечает предложение N3248: noexcept Предотвращает проверку библиотек. Это, в свою очередь, послужило основой для N3279: Консервативное использование noexcept в Библиотеке. Этот документ определяет узкие и широкие контракты, как и N3248:

Широкие контракты

Широкий контракт для функции или операции не укажите любое поведение undefined. Такой договор не имеет никаких предварительных условий: Функция с широким контрактом не предусматривает дополнительного времени выполнения ограничений на его аргументы, на любое состояние объекта или на любые внешние глобальное государство. Примеры функций, имеющих широкие контракты, vector:: begin() и vector:: at (size_type). Примеры функции, не имеющие широкого контракта, будут иметь вектор:: front() и vector:: operator [] (size_type).

Узкие контракты

Узкий контракт - это контракт, который не является широким. Узкие контракты на функции или операции приводят к поведению undefined при вызове в что нарушает документированный контракт. Такой договор указывает, по крайней мере, одно предварительное условие, включающее его аргументы, объект состояния или некоторого внешнего глобального состояния, такого как инициализация статический объект. Хорошие примеры стандартных функций с узкими контракты: vector:: front() и vector:: operator [] (size_type).

и рекомендует:

Каждая функция библиотеки имеет широкий контракт, который согласен с LWG не может бросать, следует отмечать как безоговорочно noexcept.

и подразумевает, что функции с узкими контрактами не должны быть noexcept, которые подкреплены LWG issue 2337, в котором говорится:

[...] Эти соображения дизайна переопределяют нашу общую политику, исключая узкоконтрактные функции. [...]

Итак, если мы хотим быть консервативными и следовать стандартной библиотечной практике, тогда казалось бы, что operator[] не имеет широкого контракта, он не должен быть помечен noexcept.

Ответ 2

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

T& operator[](size_t i)
{
    static T default;
    if (i >= length && length > 0)
    {
        return v[length - 1];
    }
    else if (i >= length && length == 0)
    {
        return default;
    }
    return v[i];
}

Это нестандартно, но, на мой взгляд, приемлемо, если вы четко документируете поведение. Это позволяет исключить и гарантировать отсутствие сигнала.

Ответ 3

Тот факт: либо вы используете noexcept, либо нет, функция все равно может нажать SIGSEGV.

Теперь, я думаю, что ответ субъективен и зависит от одной личной перспективы. Что вы ожидаете от функции noexcept? Ожидаете ли вы, что он никогда не сработает (даже если noexcept не предлагает такую ​​гарантию)? Как вы будете обрабатывать небезопасную функцию по сравнению с "безопасной" функцией? Будет ли это большой разницей в проекте? Если ваш ответ "да", вы, возможно, не должны использовать noexcept.

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

От Bjarne Stroustroup "Язык программирования С++", 4-е издание:

Объявление функции noexcept может быть наиболее ценным для программиста рассуждения о программе и для компилятора, оптимизирующего программу. программисту не нужно беспокоиться о предоставлении try-clauses (для решения с отказами в функции noexcept), и оптимизатор не должен беспокоиться о путях управления обработкой исключений.

Итак, нецелесообразно ли отмечать noexcept функцию, которая не является безопасно?

Моя личная точка зрения "Нет", это не плохая практика или "аморальность", чтобы использовать ее, тем более, что она может иметь некоторые преимущества.