constメンバ関数など

僕は2年前くらいに「ix作ります!」と言ったきり、恥ずかしいことながら色々なことに手を出して逃げ続けている。
「そもそも今まで自分でまともにプログラムを作ったことがあるのか」
VBでゲームを作ったくらい…orz
Cやアセンブリに色々手を出すはいいんだけど、ほとんどまともなプログラムを組んだことがない。
これはあまりにも阿呆らしい。
そこで、受験が終わることに再びixを作ることを決意した。
これからメインで作ってみたいのはOS(「お前に作れるのか」そのツッコミは尤もです。かなり無謀な挑戦です)なので、できれば3月中に終わらせたいと思ってる。


現状


現状はこんな感じ。
まずはセオリー通り背伸びせずに電卓から。ところが、まだ優先順位が判定できない残念仕様。
まああれですよ。トークンの種類判別はできるようになってますから。
あの区切られたトークンの後ろにくっついてる"(3)"みたいなやつがそれ。

constメンバ関数


今回触れたいのは、タイトルにもなってるconstメンバ関数。
僕がC++を始めたのは3年前になるけど、この前初めて用途がわかった(笑)

// トークンクラス
class Token
{
private:
	string _str;	// トークンそのものの文字列
	int _tok_type;	// トークンの種類

public:
	Token(string arg, int type = UNKNOWN) : _str(arg), _tok_type(type) {}
	virtual ~Token() {}
	// _strのアクセサ
	string str() const{ return _str; }
	// _tok_typeのアクセサ
	int type() const { return _tok_type; }
	void set_type(int type)
	{
		_tok_type = type;
	}
	// 代入演算子(文字列を代入する)
	void operator=(const string rvalue)
	{
		_str = rvalue;
	}
	// 代入演算子(トークンをコピーする)
	void operator=(const Token tok)
	{
		this->_str = tok.str();
		this->_tok_type = tok.type();
	}
};

各トークンを保持しているクラスは今のところこうなってる。勿論、ここから機能が加わっていくとは思う。
で、問題のconstメンバ関数は_strに対するゲッタstrと、_tok_typeに対するゲッタtypeの2つ。
これらはすでにTokenクラスでオーバーロードされている演算子operator=内でも使われている(2つ目の方)。
例えば、アクセサをconstメンバ関数ではなく、普通のメンバ関数にしてしまう。

// トークンクラス
class Token
{
private:
	string _str;	// トークンそのものの文字列
	int _tok_type;	// トークンの種類

public:
	Token(string arg, int type = UNKNOWN) : _str(arg), _tok_type(type) {}
	virtual ~Token() {}
	// _strのアクセサ
	string str() /* const */ { return _str; }
	// _tok_typeのアクセサ
	int type() /* const */ { return _tok_type; }
	void set_type(int type)
	{
		_tok_type = type;
	}
	// 代入演算子(文字列を代入する)
	void operator=(const string rvalue)
	{
		_str = rvalue;
	}
	// 代入演算子(トークンをコピーする)
	void operator=(const Token tok)
	{
		this->_str = tok.str();
		this->_tok_type = tok.type();
	}
};

こんなふうに。すると次のようなコンパイルエラーが出る。

levelfour@lime ~/work $ g++ -o main main.cpp
main.cpp: メンバ関数 ‘void Token::operator=(Token)’ 内:
main.cpp:59:24: エラー: passing ‘const Token’ as ‘this’ argument of ‘std::string Token::str()’ discards qualifiers [-fpermissive]
main.cpp:60:30: エラー: passing ‘const Token’ as ‘this’ argument of ‘int Token::type()’ discards qualifiers [-fpermissive]

どういうことかというと、operator=でstrとtypeを呼ぶときに、型修飾子を無視している、とのこと。
つまり、operator=の引数tokはconstで貰っているけど、tokの値を書き換えちゃってるよ、ということ。
「え?でも、operator=では受け取った引数tokのゲッタ呼んでるだけじゃん」
とつい思っちゃうかもしれないが、コンパイラにとってはメンバ関数がオブジェクトのメンバ変数の値を書き換えないかどうかわからない。
ソースコードには「tok.str()」「tok.type()」としか書かれておらず、コンパイラはこれらのメンバ関数内でメンバ変数の値を書き換えてるかどうか調べるほどは賢くないということだ。

だから、コンパイラに「このメンバ関数はメンバ変数の値を書き換えませんよ〜」と教えるためにconstメンバ関数にする。

実際、これは入門書によく書いてあったけど、自分で失敗するまでは意味がよく理解できなかった。

付記しておくと、constの位置はソースコード中のように「関数名() const {}」または「関数名() const;」のどちらか。
クラス内での定義はたぶん後者が一般的なんだろうけどね。

【参考URL】Life like a clown
http://d.hatena.ne.jp/tt_clown/20090717/p1