第三回-03 : 関数の戻り値

前のページで関数の引数について学んだが、ここでは関数の戻り値について学ぶ。


例1

以下のプログラムは、コンソールから取得した文字列をクラスにセットし、その文字列をコンソールに表示するプログラムである。
オブジェクトを戻す関数の説明のためのプログラムであり、C 言語的な関数と C++ 的なクラスが混在するやや変則的なプログラムになっている。

#include <iostream>
#include <cstring>
using namespace std;

class samp{
    char s[80];		// 80 文字以内の文字列をメンバ変数として持つ
  public:
		// 以下のように簡単なメンバ関数であればクラス定義内で定義できる
    void show(){ cout << s << "\n";}    // クラス宣言部でも関数を定義できる
    void set(char *str){ strcpy(s,str); } // 80 文字以上の文字列をセットしないこと
};

// これは C 言語的な関数。samp クラスのオブジェクトを返す。
samp input(){
  char s[80];
  samp str;

  cout << "文字列の入力: ";
  cin >> s;    // コンソールから文字列を取得。

  str.set(s);  // 取得した文字列を samp クラスのオブジェクト str にセット

  return str;  // str を戻り値として返す。
}

int main(){
  samp ob;

  ob = input();  // コンソール取得した文字列を ob にコピー

  ob.show();     // 取得した文字列をコンソールに表示

  return 0;
}


samp クラスは、80 文字までの半角英数字の文字列を格納できるクラスである (ただし 80 文字のうち一文字はヌル文字である)。
例題用であるので、80 文字を超えた場合のエラー処理はしていない。

関数 input() はクラスのメンバ関数ではなく、C 言語的な関数である。
この関数が、samp クラスのオブジェクトを戻す関数になっている。

いつも通り、このプログラムの動作をメモリの模式図で理解してみよう。



以下、main 関数の処理に従って順に解説する。 以上である。このプログラムは以上のステップで問題なく動作するのがわかるであろう。

# (以下は細かいので自信のある人向け)
#
# 上記の説明では繁雑になるのを避けるため一箇所ごまかして記述した。
#
# input() からオブジェクト str を返すときに、環境によっては一時オブジェクトが作られることがあり、
# その場合その一時オブジェクトが ob にコピーされることになる。
# そうすると、プログラム全体でデストラクタは str、ob、一時オブジェクトの 3 回呼ばれることになる。
#
# ただしこれは環境に依存する。
# Borland C++ Compiler や gcc ver.2.95.3 では一時オブジェクトが作られたが、
# gcc ver.3.2.2 では一時オブジェクトは作られなかった。



例2

次の例を見てみよう。例1からの変更点は () の箇所である。
また、コンストラクタ内の「s='\0';」は「s=0;」に修正。(教科書が間違っている気がします)

なお、11 ページの表題に「デストラクタが 3 回呼ばれる」とあるが、上の注釈にあるようにこれは環境依存である。


#include <iostream>
#include <cstring>
#include <cstdlib>  // () malloc のため 
using namespace std;

class samp{
    char *s;   // ()  今度は配列ではなくポインタ
  public:
    samp(){ s=0; }                                   // ()  コンストラクタ。ヌルポインタで初期化
    ~samp(){ if(s) free(s); cout << "s を解放\n";}   // ()  デストラクタ。条件文は if(s!=0) と同じ。

    void show(){ cout << s << "\n";}
    void set(char *str);                             // () 
};

// これは samp クラスのメンバ関数の定義
void samp::set(char *str){             // () 
  s = (char *)malloc(strlen(str)+1);   // () 

  if(!s){                              // () メモリの割り当てに失敗したら。if(s==0) と同じ
    cout << "メモリ割り当てエラー\n";   // () 
    exit(1);                           // () 強制終了
  }                                    // ()

  strcpy(s,str);                       // () 
}                                      // () 

// これは C 言語的な関数。samp クラスのオブジェクトを返す。
samp input(){
  char s[80];
  samp str;

  cout << "文字列の入力: ";
  cin >> s;    // コンソールから文字列を取得。

  str.set(s);  // 取得した文字列を samp クラスのオブジェクト str にセット

  return str;  // str を戻り値として返す。
}

int main(){
  samp ob;

  ob = input();

  ob.show();

  return 0;
}


こちらの例では、クラスのメンバに配列 s[80] ではなく、ポインタ s と malloc によるメモリの割り当てを用いている。
malloc/free の使用については、第二回-04 を復習すること。

実行するとエラーが出る。 さらに、 ob.show() での文字列の表示がうまく機能しない。
この理由を以下の図で解説しよう。



ここでも、「メンバ関数にポインタがあるクラス」のコピーに関連してエラーが起こっている。
この問題を回避するには、やはり「代入演算子のオーバーロード」の知識が必要になる。
環境によっては「コピーコンストラクタ」の知識も必要になるだろう。

# (以下はやや細かいので自信のある人向け)
#
# 上の注釈で触れたように、Borland C++ Compiler では input() 関数が str をリターンする時に一時オブジェクトが作られるため、
# 結果的にデストラクタが三回呼ばれる。その場合はメモリの二重解放ではなくメモリを三重に解放しようとすることになる。
# 解放が二重であれ、三重であれ、結果としてはエラーになる。
#




←第三回-02:値渡し・アドレス引数・参照引数第三回課題→

第三回トップページへ

クラスから入る C++ へ戻る