Подтвердить что ты не робот

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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;

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

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

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

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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

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

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

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

Ответ 10

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

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

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

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

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

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

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

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

Это, как мне кажется, начинает архитектурное переусердство.

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

Ответ 11

Рассмотрим factory -подобный шаблон для создания объектов времени:

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

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

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