« 2009年3月 | トップページ | 2009年5月 »

2009年4月の2件の記事

【C/C++】 演算子のオーバーロード

最近C++でやたら演算子のオーバーロードをする事が多くて色々調べたり実験したりしていたんやけど、ここいらで覚書きをしておこう。

演算子をオーバーロードする目的には色々あるけど、主にオブジェクトに対して以下のような記述方法を使いたい時が上げられるかな?

obj A;
obj B;
obj C;
A = B;
A += B;
A = B + C;

もちろん + 以外にも -(sub) *(mul) /(div) &(and) |(or) ^(xor) <<(lshift) >>(rshift) などなど色々あるわけでさ。
それからコピーコンストラクタにポインタ参照、キャスト演算子などキリがないね。
で、↑のようなオブジェクト同士の演算であれば結構簡単。

class obj {
public:
    void operator = ( obj o );
    obj& operator + ( const obj& o );
    obj& operaotr += ( const obj& o );
};

こんな感じ。
明示的に整数型を引数にとりたければ(つまり A += 1; などと記述したければ)

obj& operator +=( int i );

で対応できる。もしくは

class obj {
    obj( int i );
    obj& operator += ( const obj& o );
};

でも可能。その代わり整数の1を記録するためのテンポラリオブジェクトが生成されてしまうけどね。

さてさて、この辺の関数、全て obj& を戻り値としていたけど、どうやって値を返すのか?
これも結構簡単。

class obj {
    obj& operator += ( const obj& o ) {
        // なんらかの処理
        return *this;  // 自分自身を返す
    }
};

とまぁ、*thisを返却すれば自分自身が返ります。
もちろん他のオブジェクトを返しても全く問題ないのやけど、ローカルオブジェクトだけは返却しないようにね。
一応一通りの演算子オーバーロード定義を書いておこう。
ちなみに対象は自身のオブジェクト型として、戻り型も自分自身とした場合です。
戻り型は必要に応じて変更してくださいな。

operator int( void ) { return i; }    // キャストオーバーロード   int a = (int)obj; 他のキャスト型も可能
obj& operator = ( const obj& b );   // 代入  a = b;
void operator ()( void );               // ファンクタ(関数オブジェクト)
obj& operator - ( void );              // 符号反転  -a
// 比較
bool operator ==( const obj& b );   // 比較  if ( a == b ) ...
bool operator !=( const obj& b );   // 比較  if ( a != b ) ...
bool operator <( const obj& b );   // 比較  if ( a < b ) ...
bool operator <=( const obj& b );   // 比較  if ( a <= b ) ...
bool operator >( const obj& b );   // 比較  if ( a > b ) ...
bool operator >=( const obj& b );   // 比較  if ( a >= b ) ...
// 単項演算
obj& operator += ( const obj& b );  // 加算  a += b;
obj& operator -= ( const obj& b );  // 減算  a -= b;
obj& operator *= ( const obj& b );  // 乗算  a *= b;
obj& operator /= ( const obj& b );  // 除算  a /= b;
obj& operator <<=( const obj& b );  // シフト  a <<= b;
obj& operator >>=( const obj& b );  // シフト  a >>= b;
obj& operator ++( void );               // 加算  ++a;
obj& operator ++( int );                 // 加算  a++;
obj& operator --( void );               // 加算  --a;
obj& operator --( int );                 // 加算  a--;
obj& operator &= ( const obj& b );  // AND  a &= b;
obj& operator |= ( const obj& b );  // OR  a |= b;
obj& operator ^= ( const obj& b );  // XOR  a ^= b;
// 2項演算
obj& operator + ( const obj& b );  // 加算  a + b;
obj& operator - ( const obj& b );  // 減算  a - b;
obj& operator * ( const obj& b );  // 乗算  a * b;
obj& operator / ( const obj& b );  // 除算  a / b;
obj& operator <<( const obj& b );  // シフト  a << b;
obj& operator >>( const obj& b );  // シフト  a >> b;
obj& operator & ( const obj& b );  // AND  a & b;
obj& operator | ( const obj& b );  // OR  a | b;
obj& operator ^ ( const obj& b );  // XOR  a ^ b;

さてと、ここからが本題。
上の定義を使うとオブジェクトに対するオペレータのオーバーロードが可能になる。
つまり

A = B + 1

とかがオブジェクトに対して行えるわけ。
この場合の処理順序は

  1. オブジェクトBに対して operator + (int i) 呼び出し
  2. オブジェクトAに対して operator =( const obj& b ) 呼び出し

になる。
ここで問題が起きる。もし上式を

A = 1 + B

と記述した場合、どうなるだろう?
この場合、1に対して + 演算子が処理される。でも 1 は組み込み型の int と解釈され、オブジェクトの演算子オーバーロードは影響しない。こういう時は項を入れ替えれば上の式と同じになるので、オブジェクトのオーバーロード関数で処理できるのやけど...。

で、こんな時のためにあるのがグローバル演算子のオーバーロード。
今までのは全てクラスに定義しているため処理対象はオブジェクトという事になっていたけど、グローバル演算子をオーバーロードしてしまえば組み込み型だろうが関係ないわけですな。
よって、

A = 1 + B

を処理するためには

obj& operator + ( int i, obj& b );

というグローバル演算子のオーバーロード関数を記述すれば良い。
こちらの関数はクラスの演算子オーバーロード関数と異なり、2項をとる。
上の式だと「整数 + オブジェクト」なので、第1引数が整数、第2引数がオブジェクトとなる関数を定義すればよい。
ちなみに

int operator + ( int a, int b );

のような組み込み型のみに対してのオーバーロードは禁止されているらしい。
(そりゃそうだ...

【make】 makeファイルでの多重ターゲット定義

なんか久しぶりの更新やなぁ。
ネタは色々あるのやけど、書く時間が無くて...

え〜と、またまた覚書きですな。

本職は組み込みソフトの開発エンジニアで、趣味もそれに近いものがあるんで色々な開発環境を使う必要があるわけです。Windowsのアプリだけ開発するって言うのであれば、素直にVisual Studioを使えば良いのだけど(今ならExpress Editionは無料やしね)、Linuxの環境と揃えたり、ましてや組み込み系の環境なんてどうなるか分からんので、都度開発環境を変えるなんて効率悪くてやってられへん。

という事で、まぁエディタにEmacsを使っている事もあって、僕の開発環境はす〜っかり以下のものになっているわけです。

  • Emacs
  • gcc
  • gdb
  • make
  • subversion

これなら大抵の環境でソフト開発ができるからね。
で、今回はmakeのお話。

makeってめっちゃ便利なんやけど、意外と記述が面倒やったりします。
仕様の制限とかも大きいからなんやけど、それでもタイムスタンプをチェックしてコマンド実行を制御できるってのは非常に魅力的。個人的には好きなコマンドなんで、perlやらrubyやら使わんと大抵の事はmakeで済ましてしまったりしまする。

さて、makeでのターゲットの記述方法ですが(いきなりそこかい...)こんな感じ。

ターゲット名:
    コマンド 引数

コマンドの前は必ず[TAB]である必要があります。
通常makeではdefaultのターゲットとして、allを用意していたりするので

all:
    コンパイルコマンド

ってな感じで記述するわけですが、同じターゲット名を複数書く事はできないのです。例えば

clean:
    rm -f ファイル名

clean:
    rm -rf ディレクトリ名

とかはできない。実行時にターゲット名が重複しているってエラーになるのです。
大抵は問題ないのやけど、時々これが困った事になったりして。
まぁ、回避方法はいくらでもあるのやけど、こういう場合は取り合えずターゲット名の後ろのコロン((":")を二重にしちゃいましょう。そうすれば、先に記述されたコマンドから順次実行されまする。

clean::
    rm -f ファイル名

clean::
    rm -rf ディレクトリ名

上記のように記述すれば、ファイル削除の後にディレクトリ削除が実行されますダ

« 2009年3月 | トップページ | 2009年5月 »