以前に、文字列を渡す場合に、「const char*」と、「const std::string&」などの参照コンテナで渡すのと、どちらを選択すべきか書いた。
C++ はオブジェクト指向言語であり、ポインターを渡すより、コンテナを参照渡しする方が殆どの場合有利なのは明白なのだが、C から移って来た場合、オブジェクト指向プログラムに不慣れな場合もあり、中途半端な設計(ポインターだったり、コンテナだったり)になってしまう事がある。
※思い返してみると、これは自分もそうだった・・・
今回、同じような事例として、「x、y軸」の位置を渡す方法を考えてみたい。
たとえば・・・
void set(int x, int y) { }
のような関数がある場合・・・
struct xy { int x; int y; }; void set(const xy& p) { }
このように、「x、y軸」をコンテナに入れて、参照渡しにする方が何かと都合が良い。
しかし、多くの場合、直で値を入れたい場合は、冗長では?
みたいな意見もあるのだが、通常、直で値を入れて呼ぶ事は「稀」と思うけど、それでもそのようなケースが多いなら、以下のようにすべきだろう。
struct xy { int x; int y; xy() { } xy(int x_, int y_) : x(x_), y(y_) { } ///< コンストラクターを定義 }; void set(const xy& p) { } void set(int x, int y) { set(xy(x, y)); } ///< 直で呼べるように定義を追加 set(100, 200); ///< 直接指定 set(xy(100, 200)); ///< 直で呼びたい場合、このように書ける。
ここで、構造体 xy だが、メンバーは、public になっていて、アクセサーを使わずにダイレクトにアクセスしている点を検討すべき問題として付け加えておく。
※この例では、自分の流儀でそのような仕様にしているだけで、本来は、このような単純なクラスでも、アクセサーを用意してアクセスする事が望ましいと思う、自分の裁量で、メンバーに与える影響などが少ない場合は、ダイレクトでも良いと思っている為で、このようなダイレクトなアクセス方法に注意して欲しい。
内部の変数にアクセサーでアクセスするようにする事で、安全性や最適化など得られるメリットは他に色々あるのですが、範囲が大きくなり過ぎると思うので、これは、又、別の機会に論じる事とします。
上の例では、メンバーの型は「int」でしたが、構造体 xy をテンプレートにする事で、他の型も簡単に定義できる。
typedef xy<short> s_xy; typedef xy<int> i_xy; typedef xy<float> f_xy; template<typename T> struct xy { typedef T value_type; T x; T y; void set(T x_, T y_) { x = x_; y = y_; } xy() { } xy(T v) : x(v), y(v) { } ///< カスタムコンストラクター(同じ値で初期化) xy(T x_, T y_) : x(x_), y(y_) { } ///< カスタムコンストラクター(個別に初期化) xy& operator = (const s_xy& p) { set(static_cast(p.x), static_cast (p.y)); return *this; } xy& operator = (const i_xy& p) { set(static_cast (p.x), static_cast (p.y)); return *this; } xy& operator = (const f_xy& p) { set(static_cast (p.x), static_cast (p.y)); return *this; } };
「operator = 」を使い、複数の型に対応する事で、型が違う代入をスムーズに行えるようになるが、「float -> short」や、「float -> int」は、変換出来ない場合がある為注意する必要がある。
※そのような変換が起こった場合は、例外を出すなどが必要かもしれない。
typedef T value_type;
は、テンプレートの実装では、よく使われるやり方で、元の「型」を再定義する事で、そのクラスで使われている「型」にアクセスする方法を提供する。
s_xy pos(0); ///< カスタムコンストラクターが定義されている為、x、y、を同じ値で初期化する事が出来る。 for(s_xy::value_type i = pos.x; i < (pos.x + 10); ++i) { ... }
※これは、例題なので、名前空間に入れていないが、実際に使う場合には、必ず、何らかの名前空間に入れて運用する必要がある、そうしないと、クラス名が既存のクラスとぶつかってしまう事になる。
「代数」のクラスは、自分で実装しなくても、ネットを探すと、色々なソースを見つける事が出来る、自分で実験的に実装する事で、より広範囲な理解とスキルを身につける事が出来る、そうしてから、より洗練された実装を利用しても良い。
※ある程度自分で作って、利用していると、愛着も沸くし、他の同じような実装に移るのが難しくなる、しかしながら、自分で作ったクラスはメンテナンスし易く、機能を追加したり改良するのが、楽であるメリットがある。