チラシの裏は意外と白くない

最近忘れっぽくなったので調べたことをチラシの裏に書きます

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()を呼び出す必要があった。

C++オーバーロードの仕組みを使えば、

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。