Когда для конструктора правильно выбрасывать исключение? - PullRequest
195 голосов
/ 17 сентября 2008

Когда для конструктора правильно выбрасывать исключение? (Или в случае с Целью C: когда правомерно вернуть nil?)

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

Ответы [ 25 ]

3 голосов
/ 23 декабря 2008

Если вы пишете элементы управления UI (ASPX, WinForms, WPF, ...), вам следует избегать создания исключений в конструкторе, поскольку конструктор (Visual Studio) не может обрабатывать их при создании элементов управления. Знайте свой жизненный цикл управления (управляющие события) и по возможности используйте ленивую инициализацию.

2 голосов
/ 17 сентября 2008

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

На практике вам, возможно, придется быть очень осторожным. Помните, что в C ++ деструктор не будет вызываться, поэтому, если вы выбрасываете после выделения ресурсов, вам нужно быть очень осторожным, чтобы правильно обработать это!

На этой странице подробно обсуждается ситуация в C ++.

2 голосов
/ 17 сентября 2008

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

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

1 голос
/ 17 сентября 2008

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

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

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

1 голос
/ 17 сентября 2008

Я не могу обратиться к передовому опыту в Objective-C, но в C ++ нормально для конструктора генерировать исключение. Тем более что нет другого способа обеспечить сообщение об исключительном состоянии, возникающем при создании, без обращения к методу isOK ().

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

1 голос
/ 17 сентября 2008

Создание исключения во время конструирования - отличный способ сделать ваш код более сложным. Вещи, которые кажутся простыми, внезапно становятся трудными. Например, допустим, у вас есть стек. Как вы выталкиваете стек и возвращаете верхнее значение? Что ж, если объекты в стеке могут добавить свои конструкторы (создавая временный объект для возврата вызывающей стороне), вы не можете гарантировать, что не потеряете данные (указатель стека уменьшения, создайте возвращаемое значение, используя конструктор копирования значения стек, который бросает, и теперь есть стек, который только что потерял предмет)! Вот почему std :: stack :: pop не возвращает значение, и вам нужно вызвать std :: stack :: top.

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

1 голос
/ 17 сентября 2008

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

Так что, как следствие, никогда не возвращать объект-зомби из конструктора / init.

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

Я впервые сделал зомби в Objective C много лет назад.

Как и все эмпирические правила, существует «исключение».

Вполне возможно, что специальный интерфейс может иметь контракт, в котором говорится, что существует метод "initialize", которому разрешено выполнять исключение. То, что объект, дополняющий этот интерфейс, может не отвечать правильно ни на какие вызовы, кроме установщиков свойств, пока не будет вызвана инициализация. Я использовал это для драйверов устройств в операционной системе OO во время загрузки, и это работало.

В общем, вам не нужны объекты-зомби. В таких языках, как Smalltalk с становится , все становится немного шипучим, но чрезмерное использование становиться тоже плохой стиль. Become позволяет объекту превращаться в другой объект на месте, поэтому нет необходимости в оболочке-оболочке (Advanced C ++) или шаблоне стратегии (GOF).

1 голос
/ 11 июля 2018

У вопроса ОП есть тег "независимый от языка" ... на этот вопрос нельзя ответить одинаково безопасно для всех языков / ситуаций.

Иерархия классов следующего примера C # добавляет конструктор класса B, пропуская немедленный вызов класса IDisposeable.Dispose при выходе из using основного, пропуская явное удаление ресурсов класса A.

Если, например, класс А создал конструкцию Socket при подключении к сетевому ресурсу, это, вероятно, все еще будет иметь место после блока using (относительно скрытая аномалия).

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("Initialize A's resources.");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose A's resources.");
    }
}

class B : A, IDisposable
{
    public B()
    {
        Console.WriteLine("Initialize B's resources.");
        throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose B's resources.");
        base.Dispose();
    }
}
class C : B, IDisposable
{
    public C()
    {
        Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose C's resources.");
        base.Dispose();
    }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (C c = new C())
            {
            }
        }
        catch
        {           
        }

        // Resource's allocated by c's "A" not explicitly disposed.
    }
}
1 голос
/ 28 февраля 2018

Я не уверен, что любой ответ может быть полностью независимым от языка. Некоторые языки по-разному обрабатывают исключения и управление памятью.

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

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

  • Ваша языковая поддержка исключений не очень хорошая.
  • У вас есть веская причина для использования new и delete
  • Ваша инициализация требует интенсивной работы процессора и должна выполняться асинхронно с потоком, создавшим объект.
  • Вы создаете DLL, которая может генерировать исключения вне своего интерфейса для приложения, использующего другой язык. В этом случае проблема может быть не в том, чтобы не выдавать исключения, а в том, чтобы они перехватывались перед общедоступным интерфейсом. (Вы можете ловить исключения C ++ в C #, но есть обходы, через которые можно перейти.)
  • Статические конструкторы (C #)
0 голосов
/ 17 сентября 2008
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Cort'ы не должны делать «умных» вещей, поэтому создание исключения в любом случае не требуется. Используйте метод Init () или Setup (), если вы хотите выполнить более сложную настройку объекта.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...