среда, 5 августа 2009 г.

[comp.prog.cpp] Маленькое изобретение – проверка корректности аргументов в конструкторе

Наверняка все уже украденоизобретено до нас, но я раньше такого не видел. Поэтому порадовался, когда до этого додумался. Итак, конструктор какого-то C++ класса получает набор аргументов. На аргументы накладываются некоторые требования. Нужно порождать исключение в конструкторе, если пользователь не выполняет эти требования. Как это обычно делается? Наверное, как-то вот так:

class some_class_t {
  private :
    // Имя должно состоять из определенного набора символов и
    // длина имени должна быть не меньше N и не больше M.
    const std::string m_name;
    // Час, должен быть в диапазоне [0..23].
    const unsigned int m_hour;
    // Минута, должна быть в диапазоне [0..59].
    const unsigned int m_minute;
  ...
  public :
    // Вот и конструктор.
    some_class_t(
      const std::string & name,
      unsigned int hour,
      unsigned int minute )
      // Вот это важно! Сначала атрибуты получают значения...
      : m_name( name )
      , m_hour( hour )
      , m_minute( minute )
      {
        // ...а уже потом выполняется проверка.
        ensure_name_validity( m_name );
        ensure_hour_validity( m_hour );
        ensure_minute_validity( m_minute );
        ...
      }
  ...
};

Что здесь нехорошо? А то, что если инициализация аргументов дорогая, то мы сначала платим цену инициализации, а только затем начинаем делать проверки. Но ведь ее можно и не платить ;) Вот так:

// Вот такие валидаторы нам нужны:
const std::string &
ensure_name_validity( const std::string & arg ) { ...; return arg; }

unsigned int
ensure_hour_validity( unsigned int arg ) { ...; return arg; }

unsigned int
ensure_minute_validity( unsigned int arg ) { ...; return arg; }

class some_class_t {
  ...
  public :
    // Вот и конструктор.
    some_class_t(
      const std::string & name,
      unsigned int hour,
      unsigned int minute )
      // Вот это важно! Параметры проверяются до
      // инициализации атрибутов.
      : m_name( ensure_name_validity( name ) )
      , m_hour( ensure_hour_validity( hour ) )
      , m_minute( ensure_minute_validity( minute ) )
      {
        ...
      }
  ...
};

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

А еще эту идею можно развить до явной декларации требований к атрибутам. Вот, допустим, так можно явно прописать в коде, какие требования налагаются на параметр name конструктора нашего демонстрационного класса:

// Валидатор параметра name.
struct valid_name_t {
  const std::string & m_name;
  valid_name_t( const std::string & name )
    : m_name( ensure_name_validity( name ) )
    {}
};

// Теперь требование к аргументу name может быть указано
// прямо в описании конструктора.
class some_class_t {
  public :
   some_class_t(
     const valid_name_t & name,
     ... )
     : m_name( name )
     ...
};

По большому счету, развивать этот подход можно и дальше. Таким образом можно прописывать требования не только к параметрам конструктора, но и аргументам обычных функций/методов. В общем, если где-то нужно применять defensive programming, то есть с чем поиграться.

PS. Хочу поделиться маленькой радостью :) Количество загрузок Mxx_ru со страницы RubyForge достигло круглого числа 1400! Если учесть еще и количество загрузок через RubyGems, то получается, что Mxx_ru скачали больше 2100 раз. Что не может не радовать (хотя, смотря с чем сравнивать ;). Так что есть повод еще раз сказать спасибо всем пользователям Mxx_ru: спасибо большое!

Отправить комментарий