型豊富なプログラミング

会議を見た後、 GoingNative 2012は、C ++ 11スタイルのプログラムを作成するための「ベストプラクティス」を説明しようとすることにしました。 興味のある人向けに一連の記事が計画されていますが、

多くの場合、プログラマーは自分だけが理解できるインターフェイスを記述するか、メソッドのソースコードを調べた後に明確になるインターフェイスを記述します。 ひどくなければ、これは悪いことです。 そして、それはメソッドの名前だけではありません。



悪いインターフェースのいくつかの例



void increase_speed(double); Rectangle(int,int,int,int);
      
      





一見、すべてがそれほど悪いわけではないようですが、どのパラメーターを増加速度に渡すべきかという質問に答えることができますか? 速度の増加はパラメーターに依存しますか? 速度の増分はどの単位で測定されますか?

一般に、4つのパラメーターを持つRectangle ctorの場合、すべてが複雑です。 パラメーターは長方形の形状を示していますか? 3番目のパラメーターは、2番目のポイントの幅またはx座標ですか? まあ、など

さらに、組み込み型または組み込み型のtypedefを使用する場合、m / sのパラメーターを受け取るincrement_speedとkm / hを受け取るincrement_speedの別のバージョンを書き込むことはできません。 組み込み型を使用する場合、これは同じ関数increment_speed(double)になります。



改善されたオプション



 void increase_speed(Speed); //         Rectangle(Point topLeft, BoxWH b); // ,           .
      
      





すでに悪くはありませんが、ユニットと異なるユニットのオーバーロード機能には問題が残っています。 SIシステムですべての値を表現し、コンパイラーにコンパイル段階でユニットをチェックさせるようにすることに同意しましょう。



ディメンション情報の保存


次のテンプレートクラスが必要になります。これには、対応する基本的な測定単位(メートル、キログラム、秒)の次数が含まれます。

 template<int M, int K, int S> class Unit { //   () public: enum { m = M, kg = K, s = S }; };
      
      





すべての値は、そのようなテンプレートクラスを使用して保存されます。

 template<typename Unit> struct Value { double val; // explicit Value(double d) : val(d) {} public: /* * -    */ static constexpr int m() {return Unit::m;}; static constexpr int kg() {return Unit::kg;}; static constexpr int s() {return Unit::s;}; }; typedef Value<Unit<1, 0, -1> > Speed; //  = / typedef Value<Unit<1, 0, -2> > Acceleration; //  = // typedef Unit<1, 0, 0> M; typedef Unit<0, 0, 1> S;
      
      





使用例:

 Acceleration acc1 = Value<Unit<1, 0, -2> >(2); //  = 2 //.  . Acceleration acc2 = Value<M >(2); //  . Speed sp1 = Value<Unit<1, 0, -2> >(2); //  . Speed sp2 = Value<Unit<1, 0, -1> >(2); //  = 2 /.  .
      
      





不便な点が1つあります。 Valueクラスの演算子は説明されていません。 つまり メートルを秒で割るだけで速度が得られるまで。 除算演算子を実装しましょう(残りの演算子は類推によって実装されます)。



ユニット事業者


 template<class Value1, class Value2> auto operator/(Value1 v1, Value2 v2) -> Value<Unit<Value1::m() - Value2::m(), Value1::kg() - Value2::kg(), Value1::s() - Value2::s()> > { return Value<Unit<Value1::m() - Value2::m(), Value1::kg() - Value2::kg(), Value1::s() - Value2::s()> >(v1.val / v2.val);
      
      





これで、次のように値を初期化できます。

 Acceleration acc = Value<M>(100) / Value<S>(10) / Value<S>(1); //  = 10 //.  . Speed sp = Value<M>(100) / Value<S>(20); //  = 5 /.  .
      
      







おわりに



重要なことは、ユニットをチェックするこの手法にはオーバーヘッドがないことであり、すべてのチェックはコンパイル段階で実行されます。 速度をkm / hからm / sに変換するには、次の形式の関数を記述する必要があります。

 Speed convertSpeed(KmPerHour val);
      
      



ここで、KmPerHourクラスは、convertSpeed関数のオーバーロードを整理するために必要な基本クラスです。 できるだけ多くの一意のクラスを使用してください。これにより、関数のオーバーロードを使用し、イデオロギー的に同一の操作に異なる名前を使用する必要がなくなります(convertSpeed(KmPerHour)およびconvertSpeed(KmPerSec)に対してconvertSpeedFromKmPerHour(double)およびconvertSpeedFromKmPerSec(double))。

コードはgcc 4.6.3でチェックされました。



PS:この規格は、ユーザー定義リテラル(バージョン4.7以降のgccで)を提供します。これにより、記録は次のようになります。

 Speed sp =100m/20s; //    operator"" s(double)  operator"" d(double).
      
      





ご清聴ありがとうございました。



Upd:この記事の主なメッセージは、渡されたパラメーターのクラスを関数に書き込み、コンパイル段階で可能な限りチェックすることを怠らないことです。



All Articles