C++あれこれ
今更感があるが、C++のいくつかの内容についてまとめておく。
関数のオーバーロード
例えば、以下のようなint型の引数2つの平均値をint型で返す関数を考える。
int average(int a, int b) { return (a+b)/2; }
Cでは、float型同士の平均を求めたい場合には新たに、
float average_float(float a, float b) { return(a+b)/2; }
のような関数を定義して、average_float()を呼び出す必要があった。
int average(int a, int b) { return (a+b)/2; } float average(float a, float b) { return (a+b)/2; }
のように同名の関数として定義することが可能で、呼び出し時に引数の型が一致したほうの関数が呼び出される。
関数テンプレート
関数のオーバーロードで呼び出し側は楽にかけることが分かったが、取り扱う型の種類だけ関数自体は定義してやる必要があった。関数の定義自体も1つにしてしまうのが関数テンプレート。
template <classT> T average(T a, T b) { return (a+b)/2; }
のように記述する。呼び出し時にテンプレート引数Tを指定したデータ型に置き換えた関数を自動生成してくれる。
演算子のオーバーロード
次のようなRGBクラスを考える。R,G,Bそれぞれのデータはprivateのため直接アクセスはできない。そのため各色に対して書き込みと読み出しを行うwrite,readというpublic関数を用意している。
class RGB { private: unsigned int R; unsigned int G; unsigned int B; public: // データの書き込み void writeR(unsigned int a){ if(a > 255) { R = 255; } else { R = a; } } void writeG(unsigned int a){ if(a > 255) { G = 255; } else { G = a; } } void writeB(unsigned int a){ if(a > 255) { B = 255; } else { B = a; } } // データの読出し unsigned int readR(){ return R; } unsigned int readG(){ return G; } unsigned int readB(){ return B; } };
例えば、以下のように使用する。
int main(){ RGB rgb; // インスタンス unsigned int tmpR; unsigned int tmpG; unsigned int tmpB; tmpR = 100; tmpG = 200; tmpB = 300; // RGBの書き込み rgb.writeR(tmpR); rgb.writeG(tmpG); rgb.writeB(tmpB); // RGBを読みだして、標準出力 cout << "R = " << rgb.readR() << endl; cout << "G = " << rgb.readG() << endl; cout << "B = " << rgb.readB() << endl; return 0; }
RGBのオブジェクト同士の加算を考える。もちろん色ごとにrgb.writeR = rgb0.readR() + rgb1.readR()という感じで書いていくことも可能だが、rgb = rgb0 + rgb1のように一気に三色分の加算ができると便利になる。
int main(){ RGB rgb0; unsigned int tmpR; unsigned int tmpG; unsigned int tmpB; tmpR = 100; tmpG = 200; tmpB = 300; rgb0.writeR(tmpR); rgb0.writeG(tmpG); rgb0.writeB(tmpB); RGB rgb1; tmpR = 10; tmpG = 20; tmpB = 30; rgb1.writeR(tmpR); rgb1.writeG(tmpG); rgb1.writeB(tmpB); RGB rgb; rgb = rgb0 + rgb1; cout << "R = " << rgb.readR() << endl; cout << "G = " << rgb.readG() << endl; cout << "B = " << rgb.readB() << endl; return 0; }
実際にこれをコンパイルすると、
$ g++ rgb.cpp rgb.cpp: In function ‘int main()’: rgb.cpp:89:16: error: no match for ‘operator+’ (operand types are ‘RGB’ and ‘RGB’) rgb = rgb0 + rgb1; ^
こんな具合に、RGB型とRGB型の演算子+がない、というように怒られる。 そこで、RGB型同士の演算子"+"の挙動を定義してやる。これを演算子のオーバーロードという。オーバーロードの記述は関数とほぼ同じ感じで、以下の通り(8bitでクリップしているが、本質的ではない)。
RGB operator+(RGB a, RGB b){ RGB tmp; tmp.R = ((a.R + b.R) > 255) ? 255 : a.R + b.R; tmp.G = ((a.G + b.G) > 255) ? 255 : a.G + b.G; tmp.B = ((a.B + b.B) > 255) ? 255 : a.B + b.B; return tmp; }
さて、これで再度コンパイルをすると、
$ g++ rgb.cpp rgb.cpp: In function ‘RGB operator+(RGB, RGB)’: rgb.cpp:8:22: error: ‘unsigned int RGB::R’ is private unsigned int R; ^
今度はprivateメンバであるR,G,Bに直接アクセスしているため怒られてしまう。
こういう時にRGBのクラスの中でRGB operator+(RGB a, RGB b)をfriend指定してやればよいらしい。こうすることでRGBクラスのfriendであるoperator+(RGB a, RGB b)はRGBクラスのprivateメンバにアクセスできるようになる。
class RGB { private: unsigned int R; // 8bit unsigned int G; // 8bit unsigned int B; // 8bit public: // データの書き込み void writeR(unsigned int a){ if(a > 255) { R = 255; } else { R = a; } } void writeG(unsigned int a){ if(a > 255) { G = 255; } else { G = a; } } void writeB(unsigned int a){ if(a > 255) { B = 255; } else { B = a; } } // データの読出し unsigned int readR(){ return R; } unsigned int readG(){ return G; } unsigned int readB(){ return B; } // rgb同士の加算 friend RGB operator+(RGB a, RGB b); };
friendを使用しない書き方としては、operator+の記述を
RGB operator+(RGB a, RGB b){ RGB tmp; unsigned int tmpR,tmpG,tmpB; tmpR = ((a.readR()+b.readR()) > 255) ? 255 : (a.readR()+b.readR()); tmpG = ((a.readG()+b.readG()) > 255) ? 255 : (a.readG()+b.readG()); tmpB = ((a.readB()+b.readB()) > 255) ? 255 : (a.readB()+b.readB()); tmp.writeR(tmpR); tmp.writeG(tmpG); tmp.writeB(tmpB); return tmp; }
のようにpublicメンバだけで書けばOK。