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

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

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

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

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

как конструктор скажет программе, что она не счастлива? Я придумал несколько способов:--5-->

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

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

11 ответов


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

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


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

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

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

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

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

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;

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


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

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

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


"исключение, выбрасываемое из c'Tor", не является четырехбуквенным словом. Если объект не может быть создан правильно, C'Tor должен завершиться ошибкой, потому что вы скорее потерпите неудачу в построении, чем с недопустимым объектом.


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


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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

Это не рекомендуется другим способом, потому что если конструктор возвращается без 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;
}

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

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