C ++ Последствия игнорирования исключения из конструктора - PullRequest
5 голосов
/ 04 февраля 2010

Я искал ответ на этот вопрос, но не нашел его.

Когда объект выдает исключение в конце конструктора, является ли объект допустимым или это один из них «зависит от метода построения»?

Пример:

    struct Fraction
    {
      int m_numerator;
      int m_denominator;
      Fraction (double value,
                int denominator);
    };
    Fraction::Fraction(double value, int denominator)
    :  m_numerator(0), m_denominator(denominator)
    {
      if (denominator == 0)
      {
/* E1 */        throw std::logic_error("Denominator is zero.");
      }
      m_numerator = static_cast<int>(value * static_cast<double>(denominator));
      double actual_value = 0.0;
      actual_value = static_cast<double>(m_numerator) / static_cast<double>(m_denominator);
      double error = fabs(actual_value - value);
      if (error > 5.0E-5)
      {
/* E2 */  throw std::logic_error("Can't represent value in exact fraction with given denominator");
      }
    }

Программа:

int main(void)
{
    try
    {
        Fraction f1(3.14159264, 4); // Throws exception, E2 above.
    }
    catch (...)
    {
        cerr << "Fraction f1 not exactly representable as fraction with denom. of 4.\n";
    }

    // At this point, can I still use f1, knowing that it is an approximate fraction?

    return EXIT_SUCCESS;
}

В этом примере, можно ли использовать f1 после того, как исключение поймано, зная, что это приблизительное значение?

Элементы данных созданы и инициализированы.

Я не вижу ни одного правила языка C ++, которое было бы нарушено вышеуказанным.

Редактировать: Изменено значение ошибки дельта с 5.0E05 до 5.0E-5.

Ответы [ 9 ]

5 голосов
/ 04 февраля 2010

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

4 голосов
/ 04 февраля 2010

Ответ Джонатана правильный.Кроме того, хотя фракция может находиться в допустимом состоянии, я бы не рекомендовал использовать исключения для управления потоком и , особенно , для сообщения о состоянии объекта.Вместо этого рассмотрите возможность добавления какого-либо is_exactly_representable в API объекта Fraction, который возвращает bool.

2 голосов
/ 04 февраля 2010

добавление в конструктор = конструирование не удалось -> объект не может быть использован

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

f = new Fraction(3.14159264, 4);

В этом случае f также невозможно использовать, потому что конструктор не закончил работу, и указатель не был назначен. Деструктор не вызывается, и память освобождается , следовательно, нет никакого способа использовать объект.

Следовательно, создайте свой объект нормально, не используйте исключения, если вы собираетесь использовать класс. Используйте функцию-член is_exact (), чтобы определить, является ли она точной после построения.

2 голосов
/ 04 февраля 2010

Нет, как только область действия, определенная f1 в выходах, вы больше не можете использовать объект.Итак, в вашем коде:

try
{
    Fraction f1(3.14159264, 4); // Throws exception, E2 above.

    // f1 can be used until here
}
catch (...)
{
}

// The scope that f1 was defined in is over, so the compiler will not let
// you reference f1

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

enum FractionOption { disallowInexact, allowInexact };

Fraction::Fraction(double value, int denominator,
                   FractionOption option = disallowInexact)
{
    ...
    if ((option == disallowInexact) && (error > 5.0E-5))
    {
        throw std::logic_error("Can't represent value ...");
    }
}

Fraction f1(3.14159264, 4, allowInexact);
1 голос
/ 04 февраля 2010

Когда объект выдает исключение в конце конструктора, это объект действителен или это один из тех, «зависит от техники строительства»?

Да, зависит . Я имею в виду, это зависит от того, что вы имеете в виду объект действителен . Действительный может иметь несколько значений.

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

Однако уничтожение гарантируется в соответствии с этой схемой, указанной в C ++ / 15.2:

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

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

#include <iostream>
using namespace std;
struct A
{
    ~A() { cout<<"~A()\n"; }
};
struct B
{
    A a;
    B() { throw 1; }
    ~B() { cout<<"~B()\n"; } // never called
};
int main()
{
    try
    {
        B a;
    }
    catch (...)
    {
        cout << "caught\n";
    }
}
1 голос
/ 04 февраля 2010

после JMD

Тогда в предложении catch доступно значение f1.Ответ тоже нет.Итак, вы видите, что правила области видимости не позволяют вам даже задавать этот вопрос в коде.

Единственное, что могло бы дать отказ в существовании объекта, было бы, если бы работал его деструктор, - но он не работает, если это сделал конструкторне завершено

1 голос
/ 04 февраля 2010

Я согласен с fbrereto.

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

0 голосов
/ 04 февраля 2010

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

В любом случае, при условии, что вы используете RAII везде, все ресурсы будут правильно освобождены, и не будет никакого объекта для доступа. В случае ptr = new Foo(); переменная ptr сохраняет свое старое значение. Точно так же smartptr.reset(new Foo()); вообще не будет вызывать функцию сброса.

Обратите внимание на ошибку использования оператора new в выражениях, которые создают другие объекты: somefunc(Foo(), new Bar());. В случае сбоя конструктора Foo может произойти утечка памяти (в зависимости от порядка, в котором ваш компилятор обработал аргументы).

0 голосов
/ 04 февраля 2010

Если объект выдает исключение во время построения, это технически не делает объект недействительным.В вашем примере f1 выходит из области видимости и, следовательно, освобождается при возникновении исключения.

Если f1 был указателем, который был выделен и создан внутри блока try, и конструктор (не распределитель) вызвал исключениеВаш указатель будет на действительную, выделенную память.Будет ли объект в этой памяти содержать действительные данные, будет зависеть от вашего конструктора;в основном, если бы данные были действительны до броска, они были бы действительны и после броска.

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

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