я использую boost::units
библиотека для обеспечения физической согласованности в научном проекте. Я прочитал и попробовал несколько примеров из документации Boost. Я могу создавать свои размеры, единицы и количества. Я сделал несколько исчислений, это работает очень хорошо. Это именно то, что я ожидал, кроме этого …
В моем проекте я имею дело с временными рядами, которые имеют несколько различных единиц измерения (температура, концентрация, плотность и т. Д.) На основе шести измерений. Чтобы обеспечить безопасное и простое преобразование единиц измерения, я хотел бы добавить в каждый класс канала члена, представляющего измерения и единицы временного ряда. Кроме того, обработка данных (импорт, преобразование и т. Д.) Осуществляется пользователем, поэтому является динамичной.
Моя проблема заключается в следующем, из-за boost::units
Структура, величины внутри однородной системы, но с разными размерами, имеют разные типы. Поэтому вы не можете напрямую объявить члена, такого как:
boost::units::quantity channelUnits;
Компилятор будет утверждать, что вы должны указать размеры, используя шаблонные шевроны. Но если вы сделаете это, вы не сможете хранить разные типы количеств (например, количества с разными измерениями).
Затем я искал boost::units::quantity
объявление, чтобы узнать, есть ли базовый класс, который я могу использовать полиморфным способом. Но я не нашел его, а обнаружил, что boost::units
интенсивно использует Шаблон мета-программирования что не является проблемой, но не совсем соответствует моим динамическим потребностям, поскольку все решается во время компиляции, а не во время выполнения.
После прочтения я попытался обернуть различные количества в boost::variant
объект (приятно встретить его в первый раз).
typedef boost::variant<
boost::units::quantity<dim1>,
...
> channelUnitsType;
channelUnitsType channelUnits;
Я провел несколько тестов, и это похоже на работу. Но я не уверен в boost::variant
и Посетитель-модель.
Мои вопросы следующие:
dynamic_cast
один из них? Преобразование единиц происходит не очень часто, и только небольшое количество данных вызывает озабоченность.boost::variant
такое подходящее решение, в чем его недостатки?Я думал об этой проблеме и пришел к следующему выводу:
1. Реализация стирания типа (плюсы: приятные интерфейсы, минусы: накладные расходы памяти)
Кажется невозможным хранить без накладных расходов общее количество с общим измерением, которое нарушает один из принципов проектирования библиотек. Даже стирание типа здесь не поможет.
2. Реализовать конвертируемый тип (плюсы: приятные интерфейсы, минусы: эксплуатационные расходы)
Единственный способ, который я вижу без затрат на хранение, — это выбрать обычную (возможно, скрытую) систему, в которой все единицы преобразуются в и из. Нет затрат на память, но есть затраты на умножение почти во всех запросах к значениям, огромное количество преобразований и некоторая потеря точности с высоким показателем (подумайте о преобразовании из числа Авогадро в число 10).
3. Разрешить неявные преобразования (плюсы: приятные интерфейсы, минусы: сложнее в отладке, неожиданные эксплуатационные издержки)
Другой вариант, главным образом с практической точки зрения, состоит в том, чтобы разрешить неявное преобразование на уровне интерфейса, см. Здесь: https://groups.google.com/d/msg/boost-devel-archive/JvA5W9OETt8/5fMwXWuCdDsJ
4. Шаблон / общий код (плюсы: нет времени выполнения или нехватка памяти, концептуально верны, философия соответствует философии библиотеки, минусы: сложнее в отладке, уродливые интерфейсы, возможное раздувание кода, множество параметров шаблона везде)
Если вы спросите разработчика библиотеки, возможно, он скажет вам, что вам нужно сделать свои функции общими. Это возможно, но это усложняет код. Например:
template<class Length>
auto square(Length l) -> decltype(l*l){return l*l;}
Я использую C ++ 11 для упрощения примера здесь (это можно сделать в C++98
), а также чтобы показать, что это становится легче делать в C ++ 11 (и еще проще в C ++ 14 с decltype(auto)
,
Я знаю, что это не тот тип кода, который вы имели в виду, но он соответствует дизайну библиотеки. Вы можете подумать, ну как мне ограничить эту функцию физической длиной, а не чем-то другим? Ну, ответ в том, что вам это не нужно, однако, если вы настаиваете, в худшем случае …
template<class Length, typename std::enable_if<std::is_same<dimension_of<Lenght>::type, boost::units::length_dimension>::value>::type>
auto square(Length l) -> decltype(l*l){return l*l;}
(В лучшем случае decltype
будет делать работу SFINAE.)
На мой взгляд, вариант 4. и, возможно, в сочетании с 3. — самый элегантный путь вперед.
Пройдя глубже в моей проблеме, я прочитал две статьи, предлагающие пути решения:
Первый дает хорошие идеи для реализации интерфейса. Второй дает полный обзор того, что вы должны справиться.
Я имею в виду, что boost::units
это полный и эффективный способ обеспечения согласованности измерений во время компиляции без накладных расходов во время выполнения. В любом случае, для согласованности измерений во время выполнения с изменениями размеров вам нужна динамическая структура, которая boost::units
не обеспечивает. И вот я здесь: создаю класс юнитов, который будет в точности соответствовать моим потребностям. Больше работы для достижения, больше удовлетворения в конце …
О оригинальных вопросах:
boost::variant
работает хорошо (обеспечивает динамический boost::units
отсутствует) для этой работы. И кроме того, он может быть сериализован из коробки. Таким образом, это эффективный подход. Но это добавляет уровень абстракции для простой — я не говорю, тривиальной — задачи, которая может быть выполнена одним классом.boost::variant_cast<>
вместо dynamic_cast<>
,boost::any
может быть проще реализовать, но сериализация становится трудным путем.