組み込みC/C++

C/C++リテラシー向上のためのページ

関数に対するconst について

関数に対するconstですが、ここではconstメソッドではなく戻り値に対するconstに関して見ていこうかと思います。意図をもってconstをつけることで関数のユーザに関数の機能を明確に伝えることができますが、逆に意味のないconstをつけることで関数のユーザを悩ましたりしますので一度整理しておこうかと思います。

  • void型の関数に対するconst
const void v_func(void);

 この記述はコンパイルこそ通りますが、何の意味もないconstになります。思考のノイズになるかと思いますので消しましょう。

  • ポインタ(参照)でない関数に対するconst
const int add(int, int);

int _tmain(int argc, _TCHAR* argv[])
{
 const int foo = add(1, 2);
// add(3, 5) = 10; //const がついていなくともNG
 return 0;
}

const int add(int lhs, int rhs)
{
 return(lhs + rhs);
}

 const int fooの宣言時初期化部分がなんとなく意味がありそうな書き方ですが、ポインタや参照でない関数に対するconstは基本的には無意味な記述となります。const変数に対する宣言時初期化で右辺がconstである必要はありません(上の場合はmain関数が呼び出されたタイミングで初期化されます。) またadd(3, 5)=10;の部分ですが関数の戻り値は基本的には書き換えることができません。戻り値の実態は呼び出す側のstackないし汎用レジスタに一時的に格納されますが、それを書き換えることができたとしても再利用できません。なお基本的にはと書きましたが、実験すると下記のようなケースではコンパイルが通りました。

  •  構造体変数の戻り値に対するconst
typedef struct StByte_{
 char cLsb;
 char cMsb;
}StByte;

const StByte c_func(void);
StByte d_func(void);

int _tmain(int argc, _TCHAR* argv[])
{
//   c_func().cMsb = 0x34;//constの値に対する代入
    d_func().cLsb = 0x38;
    std::cout << d_func().cLsb << std::endl;
    return 0;
}
const StByte c_func(void)
{
    StByte stT = { 0x33, 0x34 };
    return(stT);
}
StByte d_func(void)
{
    StByte stT = { 0x33, 0x34 };
    return(stT);
}

 c_func.cMsb = 0x34 の部分は関数にconstがついている為にerrorになります。しかしd_func.cLsb=0x38 の部分は代入ができてしまいます。しかしその後のコンソールに表示されるのは関数で設定した値0x33になりますので、この代入式が意味を持つことはありません。stackないし汎用レジスタを書き換える式がOKな場合もあるということですが結局再利用することはないので意味はないかと思います。さて最後にポインタを返す関数に対するconstを見たいと思います。

  • ポインタを返す関数に対するconst
typedef struct StByte_{
 char cLsb;
 char cMsb;
}StByte;

const char* a_func(void);
char * const b_func(void);
const StByte * e_func(void);
StByte * const f_func(void);

char cSt1[5] = "abcd";
char cSt2[5] = "efgh";
char *pcSt = cSt1;

int _tmain(int argc, _TCHAR* argv[])
{
//	*a_func() = 'e'; //関数に対するconstがポインタの指す先のchar型の値への代入を禁止している
	*b_func() = 'e'; 

//	a_func()[1] = 'e';//関数に対するconstがポインタの指す先のchar型の値への代入を禁止している
	b_func()[1] = 'e';

//	a_func() = cSt2; //ポインタ自体を書き換えることはconstがついていなくても結局できない
//	b_func() = cSt2; //constがついているので当然禁止だが、ついていなくても結果は同じ

//	e_func()->cLsb = 56; //ポインタの指す先の値への代入を禁止している
	f_func()->cLsb = 65;
	
	return 0;
}

const char * a_func(void)
{
	return(pcSt);
}

char * const b_func(void)
{
	return(pcSt);
}

const StByte *e_func(void)
{
	static StByte stT = { 0x33, 0x34 };

	return(&stT);
}

StByte * const f_func(void)
{
	static StByte stT = { 0x33, 0x34 };

	return(&stT);
}

 ポインタを返す関数の場合はconstをつけることで関数を通して値を変更するなという意思表示ができます。(アドレスを返すと間接的にいくらでも値を変更することができるので、そもそもポインタを返していいのか?という議論はあります。) ただしポインタ自体の変更はconstをつけようがつけまいが変更できないので b_funcのようなポインタに対するconstは意味がありません。

 まとめ

  • ポインタ(参照)を返す関数以外の関数に対するconstは意味がないからつけない。
  • ポインタを返す関数でも 「データ型 * const」というようにポインタに対するconstは意味がないからつけない。
  • ポインタを返す関数でポインタの指し示す先を変更してほしくないという場合にconstをつける。その際は 「const データ型 *」 もしくは「データ型 const *」 という形にする。

なお、関数ポインタに対するconstは変数に対する場合と同じになるので、ポインタに対するconstも有用です。