Как обрабатывать неправильные значения в конструкторе? - PullRequest
23 голосов
/ 21 июля 2009

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

Предположим, у меня есть такой класс:

class Time
{
protected:
    unsigned int m_hour;
    unsigned int m_minute;
    unsigned int m_second;
public:
    Time(unsigned int hour, unsigned int minute, unsigned int second);
};

Хотя я хотел бы, чтобы a был успешно создан, я бы хотел, чтобы конструктор b потерпел неудачу.

Time a = Time(12,34,56);
Time b = Time(12,34,65); // second is larger than 60

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

Как конструктор скажет программе, что она не счастлива? Я подумал о нескольких методах:

  1. имеет конструктор, генерирующий исключение, и имеет обработчики в вызывающей функции для его обработки.
  2. имеет флаг в классе и устанавливает его в true, только если значения являются приемлемыми для конструктора, и программа проверяет флаг сразу после построения.
  3. имеет отдельную (возможно, статическую) функцию для вызова, чтобы проверить входные параметры непосредственно перед вызовом конструктора.
  4. изменить класс так, чтобы он мог быть создан из любых входных параметров.

Какой из этих методов наиболее распространен в промышленности? Или я что-то пропустил?

Ответы [ 11 ]

38 голосов
/ 21 июля 2009

Типичным решением является исключение.

Логика заключается в следующем: конструктор - это метод, который преобразует кусок памяти в допустимый объект. Либо это удается (обычно завершается), и у вас есть действующий объект, либо вам нужен какой-то неузнаваемый индикатор проблемы. Исключения - единственный способ сделать проблему неуязвимой в C ++.

31 голосов
/ 21 июля 2009

Другая альтернатива, для полноты:

  • Перепроектировать интерфейс так, чтобы недопустимые значения были "невозможны"

В вашем классе "Время", например, вы можете иметь:

class Time{
public:
    Time(Hours h, Minutes m, Seconds s);
//...
};

Часы, минуты и секунды являются ограниченными значениями. Например, с помощью (еще не) библиотеки Boost Constrained Value:

typedef bounded_int<unsigned int, 0, 23>::type Hours;
typedef bounded_int<unsigned int, 0, 59>::type Minutes;
typedef bounded_int<unsigned int, 0, 59>::type Seconds;
12 голосов
/ 21 июля 2009

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

Существует еще один вариант, хотя он имеет значительные последствия для стиля кодирования, поэтому его не следует воспринимать легкомысленно,

5) Не передавайте параметры в конструктор:

class Time
{
protected:
    unsigned int m_hour;
    unsigned int m_minute;
    unsigned int m_second;
public:
    Time() : m_hour(0), m_minute(0), m_second(0) {}
    // either return success/failure, or return void but throw on error,
    // depending on why the exception in constructor was undesirable.
    bool Set(unsigned int hour, unsigned int minute, unsigned int second);
};

Это называется двухфазным построением и используется именно в ситуациях, когда конструкторам нежелательно или невозможно генерировать исключения. Код, использующий nothrow new, для компиляции с -fno-exceptions, вероятно, является классическим случаем. Как только вы привыкаете к этому, это немного менее раздражает, чем вы могли подумать.

11 голосов
/ 21 июля 2009

Есть еще один возможный путь. Я не говорю, что это в любом случае предпочтительнее, я просто добавляю его для полноты:

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

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

5 голосов
/ 21 июля 2009

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

3 голосов
/ 21 июля 2009

Обычно для создания объекта Time у вас есть закрытый / защищенный конструктор и открытый статический метод фабрики. Не следует генерировать исключение из конструктора, потому что это наносит ущерб наследованию. Таким образом, ваш фабричный метод может выдать исключение, если это необходимо.

3 голосов
/ 21 июля 2009
  • пусть конструктор выдает исключение, а обработчики в вызывающая функция для обработки.

Да. Дизайн по контракту и оставить проверку предварительных условий включенной, а в случае неудачи вывести исключение. Нет больше недействительных раз.

  • имеет флаг в классе и устанавливает его в true, только если значения приемлемо для конструктора, и программа проверит флаг сразу после строительства.

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

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

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

  • изменить класс так, чтобы он мог быть создан из любых входных параметров.

Нет. Вы в основном отложите проблему.

1 голос
/ 26 октября 2010

Не думаю, что у вас есть большой выбор.

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

Код ошибки в конструкторе необходимо будет передать в качестве ссылочного параметра, и поэтому он будет выглядеть очень неудобно.

Вы можете попытаться найти способ проверить входные данные в источнике. Почему вы получаете неверные данные? Как ввод может быть неверным и т. Д.

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

Вы также можете попытаться создать метод в своем классе для обработки проверки ввода.

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

Если производительность важна и вы не хотите вызывать функцию validate дважды (пользователь вызывает ее, затем в конструкторе), я думаю, вы могли бы использовать именованный конструктор idiom, чтобы иметь CheckedConstructor и UncheckedConstructor.

Хотя, думаю, это начинает переусердствовать в архитектуре.

В конце концов, это будет зависеть от класса и варианта использования.

1 голос
/ 21 июля 2009

Просто немного поясним ответы onebyone и Timbo . Когда люди обсуждают использование исключений, обычно кто-то в конечном итоге говорит: «Исключения следует использовать в исключительных ситуациях».

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

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

Лично я бы склонялся к проверке аргументов перед построением объекта времени (что-то вроде ответа Тимбо), и тогда у меня было бы утверждение в конструкторе, чтобы проверить, что эти аргументы верны.

В случае, если вашему конструктору нужен ресурс (например, выделяет память), тогда это, ИМХО, будет ближе к исключительной ситуации, и тогда вы выдадите исключение.

1 голос
/ 21 июля 2009

Первое - лучшее, исключения - лучший способ сообщить пользователям об ошибках.

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

...