Шаблончик для размерных величин
От: rg45 СССР  
Дата: 10.09.09 21:59
Оценка: 38 (5) :)
Привет всем!

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

Для начала суть проблемы. Иногда в программах встечаются семейства функций, оперирующие некоторыми логически одинаковыми величинами, но используют при этом разные величины измерения. Например, функции, работающие со временем — одни оперируют секундами, другие миллисекундами, третьи тиками процессора и т.д. И вот, когда количество таких функций достигает некоторой величины, использовать их и не ошибаться становится сложно. И вот захотелось обзавестись каким-то приспособлением, которое защитило бы от ошибок подобного рода. И вот, получился шаблонный класс DimensionalQuantity — "размерная величина":
template<typename T>
class DimensionalQuantity
{
public:
  T operator+() const { return _create(_value); }
  T operator-() const{ return _create(-_value); }

  template <typename U> T& operator*=(U k) { _value *= double(k); return _t(*this); }
  template <typename U> T& operator/=(U k) { _value /= double(k); return _t(*this); }

  T& operator+=(const T& t) { _value += t._value; return _t(*this); }
  T& operator-=(const T& t) { _value -= t._value; return _t(*this); }

  double operator/(const T& t) const { return _value / t._value; }

  bool operator==(const T& t) const { return _value == t._value; }
  bool operator!=(const T& t) const { return _value != t._value; }
  bool operator<(const T& t) const { return _value < t._value; }
  bool operator>(const T& t) const { return _value > t._value; }
  bool operator<=(const T& t) const { return _value <= t._value; }
  bool operator>=(const T& t) const { return _value >= t._value; }

protected:
  DimensionalQuantity() : _value(1.0) {}

private:
  static T& _t(DimensionalQuantity& d) { return static_cast<T&>(d); }
  static const T& _t(const DimensionalQuantity& d) { return static_cast<const T&>(d); }
  static T _create(double value) { T t; t._value = value; return t; }
  double _value;
};

template<typename T> T operator+(DimensionalQuantity<T> a, const T& b) { return a += b; }
template<typename T> T operator-(DimensionalQuantity<T> a, const T& b) { return a -= b; }
template<typename T, typename U> T operator*(U k, DimensionalQuantity<T> t) { return t *= k; }
template<typename T, typename U> T operator*(DimensionalQuantity<T> t, U k) { return t *= k; }
template<typename T, typename U> T operator/(DimensionalQuantity<T> t, U k) { return t /= k; }

Рассчитан этот шаблон на mixing-инстанцирование. Объекты образованных типов одновременно могут выступать и как значения и как единицы измерения, например:
struct Mass : DimensionalQuantity<Mass> {};
const Mass kg;
const Mass gram = 0.001 * kg;
const Mass ton = 1000 * kg;

struct Length : DimensionalQuantity<Length> {};
const Length meter;
const Length cm = meter/100;
const Length mm = 0.1 * cm;
const Length dm = 10 * cm;
const Length km = 1000 * meter;

Объекты одинаковых типов можно сравнивать между собой, складывать и вычитать, получать частное от деления, умножать и делить на число. Основным свойством этих типов, которое защищает от ошибок, является то, что все взаимные преобразования в/из чисел возможны только с явным указанием единиц измерения. Заметьте, без явного указания единиц измерения невозможно даже получить значение, хранящееся в объекте. После же создания объекта все операции с ним выполняются независимо от того, в каких единицах измерения он был создан. Примеры:
Length h0 = 80; //error - для задания высоты необходимо указать единицы измерения
Length h1 = 80 * cm; //ok - задали высоту в сантиметрах
Length h2 = 0.8 * meter; //ok - задали высоту в метрах
bool are_equal = h1 == h2; //true

double h = height; //error - для получения численного значения необходимо указать единицы измерения
double h_mm = h1 / mm; //ok - получили высоту в миллиметрах
double h_cm = h1 / cm; //ok - получили высоту в сантиметрах
double h_dm = h1 / dm; //ok - получили высоту в дециметрах


А теперь практически полезный пример использования. Давайте создадим несколько функций-оберток для работы с временем. Обернем следующие библиотечные и системные функции:
#include <string>
#include <iostream>
#include <ctime>
#include <windows.h>

struct Time : DimensionalQuantity<Time> {};
const Time sec;
const Time ms = sec / 1000;
const Time mcs = ms / 1000;
const Time ns = mcs / 1000;
const Time min = 60 * sec;
const Time hour = 60 * min;
const Time day = 24 * hour;
const Time week = 7 * day;
const Time average_year = 365.25 * day;
const Time average_month = average_year / 12;

//Продолжительность тика процессора
Time perfomance_counter_tick()
{
  LARGE_INTEGER frequency;
  ::QueryPerformanceFrequency(&frequency);
  return sec / frequency.QuadPart;
}

//Время, прошедшее после старта системы
Time time_from_system_start()
{
  static const Time tick = perfomance_counter_tick();
  LARGE_INTEGER counter;
  ::QueryPerformanceCounter(&counter);
  return counter.QuadPart * tick;
}

//Усыпить поток на указанное время
void sleep(Time time) { ::Sleep(unsigned(time / ms)); }

//Время, прошедшее после 1970 года
Time time_since_1970() { return _time64(0) * sec; }

int main()
{
  std::cout << "perfomance counter tick\t= " << perfomance_counter_tick() / ns << " ns" << std::endl;
  std::cout << "time from system start\t= " << time_from_system_start() / min << " min" << std::endl;
  std::cout << "time elapsed since 1970\t= " << time_since_1970() / average_year << " average years" << std::endl;
  
  std::cout << std::endl;
  Time sleep_time = 3 * sec;
  std::cout << "sleeping " << sleep_time / sec << " sec . . ." << std::endl;
  Time sleep_begin = time_from_system_start();
  sleep(3 * sec);
  Time sleep_end = time_from_system_start();
  Time sleep_duration = sleep_end - sleep_begin;
  std::cout << "sleep duration = " 
    << int(sleep_duration / ms) << " ms = " 
    << int(sleep_duration / mcs) << " mcs = " 
    << __int64(sleep_duration / ns) << " ns" 
    << std::endl;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
шиб
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.