Шаблончик для размерных величин
От: 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;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
шиб
Re: Шаблончик для размерных величин
От: jazzer Россия Skype: enerjazzer
Дата: 10.09.09 23:57
Оценка: 18 (2)
Здравствуйте, rg45, Вы писали:

R>Привет всем!


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


Сразу скажу, что в твой код еще не вникал, просто пара ссылок:

Глава из книжки Гуртового и Абрахамса про шаблонное метапрограммирование:
http://www.boost.org/libs/mpl/doc/tutorial/dimensional-analysis.html

Готовая библиотека:
http://www.boost.org/doc/html/boost_units.html

Интересно было бы почитать твое сравнение своего подхода и бустовского (лучше тебя, наверное, никто этого не сделает).
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Шаблончик для размерных величин
От: rg45 СССР  
Дата: 11.09.09 00:54
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Глава из книжки Гуртового и Абрахамса про шаблонное метапрограммирование:

J>http://www.boost.org/libs/mpl/doc/tutorial/dimensional-analysis.html

J>Готовая библиотека:

J>http://www.boost.org/doc/html/boost_units.html

Спасибо за ссылки.

J>Интересно было бы почитать твое сравнение своего подхода и бустовского (лучше тебя, наверное, никто этого не сделает).


Конечно, функциональность, реализованная в бусте, на много шире, чем у меня. Мой DimensionalQuantity — это, по сути, урезанный по возможностям boost::quantity. Если посмотреть, то даже определения этих шаблонов похожи. У меня изначально даже был в шаблоне второй параметр: BaseT = double точно такой же как и в бустовском варианте. Но потом я счел его неактуальным и упростил Очень важное отличие бустовского варианта — это возможность умножения и деления величин разной размерности, а также возведение в степень с получением величин новой размерности. У меня оказался реализованным только вариант деления величин с одинаковой размерностью и получения безразмерной Также мощная фича — использования пользовательских типов в качестве несущих. Примеры с комплексными типами и кватернионами впечатляют. Хотя, если сравнить отношения реализованной функциональности к количеству строк кода, то думаю, я с бустом могу еще и посоревноваться
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Шаблончик для размерных величин
От: SleepyDrago Украина  
Дата: 11.09.09 08:49
Оценка:
Здравствуйте, rg45, Вы писали:

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


я не совсем понял чем это в принципе отличается от
typedef double Time;
и далее по тексту

кроме запрета некоторых операций ну и конструктора ставящего единицу.
запрет операций в общем то вещь хорошая но удобство от этого меньше затраченных усилий. Разве что много было багов от сложения килограммов и сантиметров.
скорость как метр/секунда оно уже не проверит.
Re[2]: Шаблончик для размерных величин
От: rg45 СССР  
Дата: 11.09.09 20:32
Оценка:
Здравствуйте, SleepyDrago, Вы писали:

SD>Здравствуйте, rg45, Вы писали:


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


SD>я не совсем понял чем это в принципе отличается от

SD>
SD>typedef double Time;
SD>


Отличается, вот именно, в принципе.

Допустим, мы реализовали функцию:
void sleep(Time);

Следующие вызовы этой функции эквивалентны:
void sleep(3 * sec);
void sleep(3000 * ms);

После этого мы можем захотеть изменить базовую единицу измерения:
const Time ms;
const Time sec = 1000 * ms;

В моей реализации я буду уверен на все сто, что после этого ничего не поломалось. А тебе прийдется перелопатить свой код, в поисках места, где какой-то лоботряс написал sleep(3000) вместо sleep(3000 * ms), и до изменения базовой единицы ошибка не проявлялась.

Короче говоря, принципиальная разница между моей и твоей реализациями в том, что те ошибки, которые я отловлю на этапе компиляции, тебе прийдется отлавливать во время выполнения.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Шаблончик для размерных величин
От: rg45 СССР  
Дата: 12.09.09 04:51
Оценка:
Здравствуйте, SleepyDrago, Вы писали:

SD>я не совсем понял чем это в принципе отличается от

SD>
SD>typedef double Time;
SD>и далее по тексту
SD>

SD>кроме запрета некоторых операций ну и конструктора ставящего единицу...

По поводу единицы. Я легко могу заменить ее на произвольное ненулевое значение и от этого ничего не изменится. Это деталь реализации на которой вообще не стоит фокусировать внимание.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: Шаблончик для размерных величин
От: gear nuke  
Дата: 12.09.09 05:59
Оценка:
Здравствуйте, rg45, Вы писали:

R>Допустим, мы реализовали функцию:

R>
R>void sleep(Time);
R>

R>Следующие вызовы этой функции эквивалентны:
R>
R>void sleep(3 * sec);
R>void sleep(3000 * ms);
R>


В будущем Стандарте планируется делать так:
  sleep_for(std::chrono::seconds(3));
  sleep_for(std::chrono::milliseconds(3000));

одна из реализаций порождает такой код:
    lea    eax, DWORD PTR $T57053[esp+104]
    push    eax
    movzx    eax, BYTE PTR _KernelMode
    xor    ebx, ebx
    push    ebx
    push    eax
    mov    DWORD PTR $T57053[esp+116], -30000000    ; fe363c80H
    call    DWORD PTR __imp__KeDelayExecutionThread@12

Какова цена вопроса в твоём случае? Будут ли рантайм вычисления с double?
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Re[3]: Шаблончик для размерных величин
От: minorlogic Украина  
Дата: 12.09.09 08:49
Оценка:
Вопрос не по теме, вы будете использовать свой велосипед или бустовский ? чем будет обоснованно использование той или иной библиотеки ?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[4]: Шаблончик для размерных величин
От: rg45 СССР  
Дата: 12.09.09 13:49
Оценка: 10 (1)
Здравствуйте, minorlogic, Вы писали:

M>Вопрос не по теме, вы будете использовать свой велосипед или бустовский ? чем будет обоснованно использование той или иной библиотеки ?


Ну разумеется, бустовский. Откровенно говоря, о существовании в бусте такой библиотеки я просто не знал, спасибо jazzer-у, открыл глаза Ну чем обусловлено, думаю, объяснять не надо — тем чтоб свести к минимуму велосипедостроение.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: Шаблончик для размерных величин
От: rg45 СССР  
Дата: 12.09.09 14:01
Оценка:
Здравствуйте, gear nuke, Вы писали:

GN>...

GN>Какова цена вопроса в твоём случае? Будут ли рантайм вычисления с double?

Конечно, должен признать, что в моем решении в месте с типом double зашит оверхед несколько больший, чем это могло бы быть. Но я ж уже покаялся и намерен вместо этого велосипеда использовать бустовскую библиотеку. Но, строго говоря, получаемая безопасность кода не будет абсолютно бесплатной даже при использовании бустовской библиотеки. Если уж случится такое, что даже бустовский вариант окажется слишком дорогим, вот тогда уже действительно прийдется изобретать что-то свое, заточенное под конкретную задачу. Но я думаю, такие варианы довольно редки, скорее исключение, чем правило.
--
Не можешь достичь желаемого — пожелай достигнутого.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.