2012-06-14 10 views
19

Jestem obecnie projektowaniu API, gdzie chcę, aby użytkownik, aby móc napisać kod tak:Jak obsługiwać jednostki w C++ interfejs

PowerMeter.forceVoltage(1 mV); 
PowerMeter.settlingTime(1 ms); 

Obecnie robimy to za pomocą definiuje takie jak:

#define mV *1.0e-03 

to sprawia, że ​​bardzo wygodne dla użytkownika, aby napisać swój kod, a to jest bardzo czytelny, ale oczywiście ma wady też:

int ms; 

Rzucenie niektórych błędów kompilatora, które są trudne do zrozumienia. Więc szukam lepszego rozwiązania.

próbowałem nowego C++ 11 literały, ale to wszystko, co mogłem osiągnąć to:

long double operator "" _mV(long double value) { 
    return value * 1e-3; 
} 
PowerMeter.forceVoltage(1_mV); 

w końcu API nie dba o jednostce jak Volt czy drugi, ale trwa tylko liczby , więc nie chcę robić żadnego sprawdzenia, czy naprawdę wpisujesz Volts in forceVoltage, czy nie. To powinno być również możliwe:

PowerMeter.forceVoltage(2 ms); 

Każdy pomysł oprócz pozostania z definicjami?

+2

Czy możesz przekazać swoje jednostki jako oddzielną zmienną? 'PowerMeter.forceVoltage (2," ms ");' A może całe wyrażenie jako ciąg znaków? – Blender

+0

Mogłem to zrobić, ale nie jest to naturalny sposób, w jaki użytkownik chciałby programować. – schnaufi

+9

Dlaczego ... Czy chcesz określić jednostki miary, ale nie chcesz sprawdzić, czy są prawidłowe? To nie ma sensu. Co więcej, twój "użytkownik" zna C++, ale troszczy się o "naturalną drogę" ... Prawie nie do pomyślenia. Dlaczego nie uwzględnić go po prostu w nazwie funkcji? – keltar

Odpowiedz

1

wolę uniknąć makra gdzie kiedykolwiek może i jest to przykład, w którym powinno być możliwe. Jeden lekkie rozwiązanie, które daje odpowiednie wymiary to:

static double m = 1; 
static double cm = 0.1; 
static double mV = 0.001; 

double distance = 10*m + 10*cm; 

Odzwierciedla to również pojęcie fizyczne, które jednostki są czymś, co jest mnożona przez wartość.

+1

Dodatkowy komentarz: Możesz chcieć użyć przestrzeni nazw dla tego, a użytkownik twojego API może wtedy zdecydować, czy woli zaśmiecać swoją przestrzeń nazw, czy zrobić setVoltage (10 * CoolUnits :: m). Ale wciąż lepsze niż definiowanie rzeczy. –

+0

Śliczne z niewielkim obciążeniem, ale to za mało. Problem polega na tym, że $ 1m \ neq 1 $, ale do $ m $. Oznacza to, że 1 mln USD = 100 cm $, ale samo urządzenie również jest terminem. Jest to problem, gdy dana osoba próbuje dodać temperaturę na odległość. Twoje rozwiązanie na to pozwoli. – user1512321

17

jak o, a nie obracając go wokół kawałka przez tworzenie grup (ms, MV) dla różnych prądów

np

PowerMeter.forceVoltage(mV(1)); 
PowerMeter.settlingTime(ms(1)) 

Jest to dość oczywiste dla użytkownika i prawdopodobnie nie jest trudne do odczytania, a dodatkowo otrzymywałbyś sprawdzanie typu za darmo. posiadanie wspólnej klasy bazowej dla różnych jednostek ułatwiłoby wdrożenie.

+3

Boost.Date_Time używa tego podejścia dla jego typów 'time_duration' (http://www.boost.org/doc/libs/1_49_0/doc/html/date_time/posix_time.html#date_time.posix_time.time_duration). –

8

Możesz zobaczyć bibliotekę "C++ Units" z Calum Grant jako dobry przykład tego, jak to zaimplementować. Biblioteka jest nieco przestarzała, ale wciąż warta zobaczenia lub może być użyta.

Także myślę, że może to być interesujące, aby przeczytać: „Applied Template Metaprogramming in SI UNITS: the Library of Unit-Based Computation

Jest jeszcze jedna dobra biblioteka: UDUNITS-2 których:

zawiera bibliotekę C dla jednostek wielkości fizycznych i jednostki -definicja i narzędzie do konwersji wartości.

+2

+1 Poza wskaźnikami, ** wymiary ** są najważniejszymi częściami. Po ustaleniu wymiarów stosunki (prawie) są dostępne za darmo. –

1

Rozważmy stosując enum dla jednostek i przekazać go jako drugi parametr:

namespace Units 
{ 
    enum Voltage 
    { 
     millivolts = -3, 
     volts = 0, 
     kilovolts = 3 
    }; 

    enum Time 
    { 
     microseconds = -6, 
     milliseconds = -3, 
     seconds = 0 
    }; 
} 

class PowerMeter 
{ 
public: 
    void forceVoltage(float baseValue, Units::Voltage unit) 
    { 
     float value = baseValue * std::pow(10, unit); 
     std::cout << "Voltage forced to " << value << " Volts\n"; 
    } 

    void settlingTime(float baseValue, Units::Time unit) 
    { 
     float value = baseValue * std::pow(10, unit); 
     std::cout << "Settling time set to " << value << " seconds\n"; 
    } 
} 

int main() 
{ 
    using namespace Units; 
    PowerMeter meter; 
    meter.settlingTime(1.2, seconds); 
    meter.forceVoltage(666, kilovolts); 
    meter.forceVoltage(3.4, milliseconds); // Compiler Error 
} 

Owijanie Units nazw wokół teksty stałe unika zanieczyszczające globalną przestrzeń nazw z nazwami jednostek. Używanie w ten sposób wyliczeń również wymusza w czasie kompilacji, aby właściwa jednostka fizyczna była przekazywana do funkcji składowych.

+2

Widząc jak OP próbował literatury C++ 11, bardziej odpowiednia byłaby klasa "enum". – chris

+0

@chris: Z 'enum class', nie ma możliwości pobrania wszystkich nazw jednostek w bieżącym zakresie za pomocą dyrektywy' using'. Musisz więc poprzedzić każdą jednostkę (np. 'Voltage :: volt'). –

+0

To prawda.Myślę, że to zależy od tego, jakiego rodzaju kodu użytkownik chce napisać. – chris

2

Oto, co wymyśliłem ...prawie taki sam pomysł jak Anders K, ale odkąd napisał kod, będę pisać go:

#include <iostream> 

using namespace std; 

class MilliVoltsValue; 
class VoltsValue; 

class VoltsValue 
{ 
public: 
    explicit VoltsValue(float v = 0.0f) : _volts(v) {/* empty */} 
    VoltsValue(const MilliVoltsValue & mV); 

    operator float() const {return _volts;} 

private: 
    float _volts; 
}; 

class MilliVoltsValue 
{ 
public: 
    explicit MilliVoltsValue(float mV = 0.0f) : _milliVolts(mV) {/* empty */} 
    MilliVoltsValue(const VoltsValue & v) : _milliVolts(v*1000.0f) {/* empty */} 

    operator float() const {return _milliVolts;} 

private: 
    float _milliVolts; 
}; 

VoltsValue :: VoltsValue(const MilliVoltsValue & mV) : _volts(mV/1000.0f) {/* empty */} 

class PowerMeter 
{ 
public: 
    PowerMeter() {/* empty */} 

    void forceVoltage(const VoltsValue & v) {_voltsValue = v;} 
    VoltsValue getVoltage() const {return _voltsValue;} 

private: 
    VoltsValue _voltsValue; 
}; 

int main(int argc, char ** argv) 
{ 
    PowerMeter meter; 

    meter.forceVoltage(VoltsValue(5.0f)); 
    cout << "Current PowerMeter voltage is " << meter.getVoltage() << " volts!" << endl; 

    meter.forceVoltage(MilliVoltsValue(2500.0f)); 
    cout << "Now PowerMeter voltage is " << meter.getVoltage() << " volts!" << endl; 

    // The line below will give a compile error, because units aren't specified 
    meter.forceVoltage(3.0f); // error! 

    return 0; 
} 
1

wolę rozwiązania od Anders K, jednak można skorzystać z szablonu, aby zaoszczędzić trochę czasu realizacji wszystkich jednostek jako separte klasy, które mogą być czasochłonne i podatne na błędy, ponieważ może trzeba napisać dużo kodu ręcznie:

enum Unit { 
    MILI_VOLT = -3, 
    VOLT = 0, 
    KILO_VOLT = 3 
}; 

class PowerMeter 
{ 
public: 

    template<int N> 
    void ForceVoltage(double val) 
    { 
     std::cout << val * pow(10.0, N) << endl; 
    }; 
}; 

wykorzystanie takich jak ten:

 PowerMeter pm; 
     pm.ForceVoltage<MILI_VOLT>(1); 
     pm.ForceVoltage<VOLT>(1); 
     pm.ForceVoltage<KILO_VOLT>(1); 
5

Spójrz na Boost.Units. Oto niektóre przykładowy kod:

quantity<energy> 
work(const quantity<force>& F, const quantity<length>& dx) 
{ 
    return F * dx; // Defines the relation: work = force * distance. 
} 

... 

/// Test calculation of work. 
quantity<force>  F(2.0 * newton); // Define a quantity of force. 
quantity<length> dx(2.0 * meter); // and a distance, 
quantity<energy> E(work(F,dx)); // and calculate the work done. 
+0

Istnieje również [PhysUnits-CT-Cpp11] (https://github.com/martinmoene/PhysUnits-CT-Cpp11), mała biblioteka wyłącznie dla nagłówków C++ 11, C++ 14 do analizy wymiarowej w czasie kompilacji i manipulacja i konwersja jednostek/ilości. Prostsze niż Boost.Units, zależy tylko od standardowej biblioteki C++, tylko SI, integralne moce wymiarów. –

1

Przed zwariować z niczego bardziej skomplikowana, kiedy piszesz nowy kod, który pobiera liczbę jako argument należy wymienić swoje metody, jak to tak, że jest w 100% jasne:

PowerMeter.forceInMilliVolts(...) 
PowerMeter.settlingTimeInSeconds(...) 

I podobnie używać zmiennych z odpowiednimi nazwami np:

int seconds(10); 
int milliVolts(100); 

ten sposób, że nie ma znaczenia, czy masz do konwersji, to s do jasności, co robisz, np.

PowerMeter.settlingTimeInSeconds(minutes*60); 

Gdy jesteś gotowy coś mocniejszy przenieść do tego, czy naprawdę trzeba, ale upewnij się, że nie tracą wyrazistości której urządzenie jest używane.