21

私は現在、ユーザーが次のようなコードを記述できるようにするAPIを設計しています。

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

現在、次のような定義を使用してこれを行っています。

#define mV *1.0e-03

これにより、ユーザーがコードを書くのが非常に便利になり、非常に読みやすくなりますが、もちろん欠点もあります。

int ms;

理解しにくいいくつかのコンパイラエラーをスローします。だから私はより良い解決策を探しています。

新しいC++11リテラルを試しましたが、これで達成できるのは次のとおりです。

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

結局、APIはボルトや秒のような単位を気にせず、数値だけを受け取るので、実際にforceVoltageにボルトを入力したかどうかをチェックしたくありません。したがって、これも可能であるはずです。

PowerMeter.forceVoltage(2 ms);

定義にとどまる以外に何かアイデアはありますか?

4

9 に答える 9

18

代わりに、さまざまな電流のクラス(ms、mV)を作成して、少し回転させてはどうでしょうか。

例えば

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

それはユーザーにはかなり明白であり、間違いなく読むのは難しくありません。さらに、タイプチェックを無料で受けることができます。異なるユニットに共通の基本クラスがあると、実装が簡単になります。

于 2012-06-14T06:29:58.343 に答える
9

これを実装する方法の良い例として、CalumGrantのライブラリ「 C++Units 」を見ることができます。ライブラリは少し古くなっていますが、それでも見る価値があるか、使用する価値があるかもしれません。

また、「 SI単位系での応用テンプレートメタプログラミング:単位系計算のライブラリ」を読むのも面白いかもしれません。

もう1つの優れたライブラリがあります:UDUNITS-2これは:

物理量の単位用のCライブラリと、単位定義および値変換ユーティリティが含まれています。

于 2012-06-14T06:32:35.350 に答える
8

ユニットのリテラルまたはマクロを定義する代わりに、 C++11のコンパイル時のユニットの有理算術サポートを使用できます。

于 2012-06-14T06:20:46.027 に答える
6

Boost.Unitsを見てください。次にいくつかのサンプルコードを示します。

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.
于 2012-06-14T14:05:25.393 に答える
2

私はできる限りマクロを避けることを好みます、そしてこれはそれが可能であるはずの例です。正しい寸法を提供する軽量ソリューションの1つは、次のとおりです。

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

double distance = 10*m + 10*cm;

これは、単位は値を掛けたものであるという物理的な概念も反映しています。

于 2012-06-14T12:10:11.177 に答える
2

これが私が思いついたものです...AndersKとほぼ同じアイデアですが、コードを書いたので、投稿します。

#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;
}
于 2012-06-14T06:46:20.973 に答える
1

ユニットにを使用することを検討し、enumそれを2番目のパラメーターとして渡します。

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
}

Units名前空間を列挙型にラップすることで、グローバル名前空間がユニット名で汚染されるのを防ぐことができます。このように列挙型を使用すると、コンパイル時に適切な物理単位がメンバー関数に渡されるようになります。

于 2012-06-14T06:34:43.287 に答える
1

私はAndersKのソリューションを好みますが、テンプレートを使用して、すべてのユニットを個別のクラスとして実装する時間を節約できます。これは、手作業で多くのコードを作成する必要があるため、時間がかかり、エラーが発生しやすくなります。

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;
    };
};

このように使用します:

        PowerMeter pm;
        pm.ForceVoltage<MILI_VOLT>(1);
        pm.ForceVoltage<VOLT>(1);
        pm.ForceVoltage<KILO_VOLT>(1);
于 2012-06-14T08:05:59.090 に答える
1

もっと複雑なことに夢中になる前に、引数として量をとる新しいコードを書くときはいつでも、100%明確になるようにメソッドに次のような名前を付ける必要があります。

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

同様に、正しい名前の変数を使用します。例:

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

このように、あなたが変換しなければならないかどうかは問題ではありません、それはあなたが何をしているのかまだ明らかです例えば

PowerMeter.settlingTimeInSeconds( minutes*60 );

より強力な何かの準備ができたら、本当に必要な場合は、使用されているユニットの明確さを失わないようにしてください。

于 2012-06-14T15:09:31.870 に答える