Как обрабатывать неверные значения в конструкторе?

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

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

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); }; 

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

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

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

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

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

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

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

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

Еще одна альтернатива для полноты:

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

Например, в вашем classе «Время» вы можете:

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

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

 typedef bounded_int::type Hours; typedef bounded_int::type Minutes; typedef bounded_int::type Seconds; 

Обычно я бы сказал (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 , вероятно, является classическим. Как только вы привыкнете к этому, это немного менее раздражает, чем вы могли бы сначала подумать.

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

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

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

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

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

  • конструктор выдает исключение и имеет обработчики в вызывающей функции для его обработки.

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

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

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

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

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

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

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

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

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

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

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

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

Я не думаю, что у тебя есть выбор.

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

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

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

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

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

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

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

Думаю, это начинает архитектурное переутомление.

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

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

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

Рассмотрим заводскую модель для создания объектов времени:

 static bool Time::CreateTime(int hour, int min, int second, Time *time) { if (hour <= 12 && hour >= 0 && min < 60 && min >= 0 && second < 60 && second >= 0) { Time t(hour, min, second); *time = t; return true; } printf("Your sense of time seems to be off"); return false; } Time t; if (Time::CreateTime(6, 30, 34, &t)) { t.time(); // :) } else { printf("Oh noes!"); return; } 

Это делает предположение, что Время имеет:

  • конструктор по умолчанию
  • конструктор копирования
  • оператор присваивания копии