Hatena::ブログ(Diary)

j○kerfin○たんの□グ

2009-11-19 建前の練習

C,C++言語での三項演算子の正しい(?)使い方

C/C++(おまけC#)言語での私なりの三項演算子の使い方。

三項演算子って何の?ではなく、どこで使うの?というtips的なページが少ないので書いてみたり。

なお、Java言語と書かないのは私がJavaにおける自分流のスタイルを確立していないので何とも言えないから。


  • 三項演算子とは?

<条件> ? <真の場合の値> : <偽の場合の値>というのが構文。

例えば

int abs( int x )
{
	return x < 0 ? -x : x;
}	

とても分かりやすく言うと

int abs( int x )
{
	if ( x < 0 )
		return -x;
	else
		return x;
}

さて、ここで問題なのが毎回if文を使えば三項演算子なんていらないんじゃね?ということである。

確かにすべてif文で書ければ三項演算子なんていらないんだろうが、そんなことはない。

if文だと綺麗に書けないところがあるのである。

とりあえず、三項演算子の主だった2通りの場面をあげてみる。


  • const変数

さて、まずはconst変数をどこで使うの?という話をしましょう。

基本的にconst変数は「その変数はこれ以降変わりませんよ(コンパイラ様、最適化お願いしますよ?)」と明示的に教える

それが何の役に立つのかというとプログラムを理解する時である。


int some_function()
{
	int foo = 0;
	// 以下、だらだらと何かが続く...
}
int some_function()
{
	const int foo = 0;
	// 以下、だらだらと何かが続く...
}

上述の2つのサンプルの違いはconstだけであるが、意味は大きく異なる。

前者の場合は変数fooが後続の処理で変更される可能性があるが、後者の場合は0で不変である。

プログラムを読む際、前者の場合は「fooは変更されるかもしれないから、fooの値はちゃんと自分でトレースしながら読んでね?」と読める。

後者の場合は「fooはconst変数として値が確定しているので、fooの値は覚えなくてもここを見ればすぐに分かるよ?」と教えてくれる。

というわけで、後者の方がプログラムの把握がしやすいのである。


まぁ、ここまではプログラムを書いている人なら常識なのだろう。

で、それが三項演算子とどう関係あるのさ?ということであるが、const変数は三項演算子を使わないとうまく使えないのである。


一口に定数といっても定数には主に二通りある。

コンパイル時定数と実行時定数(たぶん、この言い方であってるよなぁ?)である。

コンパイル時定数とはコンパイルの時に定数としてわかるものである。

例えば、

static const double pi = 3.14;

と書けば、コンパイル時には変数piの値が3.14だと分かる。

コンパイル時定数はマクロに還元できると考えれば分かりやすい。

コンパイル時に分かるということは、プリプロセッサ処理の段階でもわかるのである。

#define PI 3.14

とかけばいいのである。


コンパイル時定数に対して実行時定数というものがある。

int some_function( int x )
{
	const int foo = x + 1;
	// なんらかの処理
}

この場合、const変数fooはコンパイル時には値が確定しない(コンパイラの最適化はここでは無視してね♪)。

some_functionという関数がよばれてみるまで変数xが分からないからである。


さて、このように定数をコンパイル時定数と実行時定数とで分けてみてみると意外と三項演算子の使い道が見えてくる。

三項演算子は主に実行時定数の方に対して使う。

当たり前であるが、コンパイル時定数であるものが実行時の処理なぞ必要とするはずがないのであるから三項演算子なぞ不要である。


実行時定数はコンパイル時定数に比べて"実行時の"定数ということでそれなりに柔軟性がある。

class A
{
public:
	int hoge( int x );
}

int some_function( A* a )
{
	const int foo = (クラスAのポインタaがnullの時は0,nullではないときはa->hoge(0)の返り値を返したい );
}

このように場合分け(aがNULLの時にa->hoge(0)なんてするとセグメンテーションフォルトが起きるので場合分けが必須)で定数を決めることができるのである。

三項演算子を書くと以下のようになる。

	const int foo = a == NULL ? 0 : a->hoge( 0 );

これを安易に

	const int foo;
	if ( a == NULL )
		foo = 0;
	else
		foo = a->hoge( 0 );

なんて書き変えてはいけない。

const変数は変数宣言したところで初期化をしなければならない。

そのため、同じことをif文で書こうと思うとご丁寧に関数が必要になる

int bar( A* a )
{
	if ( a == NULL )
		return 0;
	else
		return a->hoge( 0 );
}

int some_function( A* a )
{
	const int foo = bar( a );
}

この場合constな変数fooを理解するためにはもうひとつ別のbarなる関数を読む必要があり、可読性が落ちることになる。


const変数の値を求めるのに、膨大な計算が必要なら確かに関数を呼ぶ方がスタイルとしては正解だが、if文一つかければすみそうな上述の場合には三項演算子を使うのが正解だろう。

特にC/C++ではポインタを用いれるせいかNULLの時とそうでないときの比較というのが多発する。

そういう場面で三項演算子はとても綺麗にかけて便利だと私は思う。


なお、C#でも似たようにnullで場合分けをすることが多い。

特にnull許容型が登場してからnullがあちこちにはびこるようになっている。

そのせいもあってか、??演算子なるものが存在したりする

	string foo = null;
	string bar = foo ?? "default message"; 

これは

	string bar = foo != null ? foo : "default message";

と同じである。

こんな演算子があること自体nullでの場合分けが多いことを示しているのだろうなぁ。


  • コンストラクタ初期化子

さて、次はC++限定な三項演算子の使いどころである。

class A
{
public:
	A( int x ) : abs_( x < 0 ? -x : x ) { }
private:
	int abs_;
}

使う意義的にはconst変数の時とまったく同じである。

関数を使えばif文でも書けたりする

class A
{
public:
	A( int x ) : abs_( abs( x ) ) { }

private:
	static int abs( int x )
	{
		if ( x < 0 )
			return -x;
		else
			return x;
	}

	int abs_;
}

こう書くと別に初期化子じゃなくて関数をコンストラクタ本体に移してやってもいいように見えてしまう。

しかし、コンストラクタ本体でしてしまうと初期化子の無駄な初期化というオーバーヘッドを食らうことになる。

これはメンバが巨大になるにつれて顕著になるため、常に初期化子でできるだけ初期化すると心がけた方がよいだろう。


上述で書いたように関数を使えばif文でコンストラクタ初期化子を用いて初期化できるが、これはコンストラクタの利点を失っている気がしてならない。

せっかくコンストラクタという「ここで初期化してますよ」と言語レベルで明示されている箇所があるのに、その中で別関数で初期化しています、というのではコンストラクタという名前自体を疑いたくなる。

これも程度の問題だが、なるべくならコンストラクタだけで初期化を完結していた方が、参照箇所が減って理解しやすいと思います。


  • で、本音は?

そんなことでこんな記事書こうと思ったのかよ、と誰かに突っ込まれそうなので本音を一つ。

実際は↑はどうでもよくてここからがネタの話です。

CとC++間で演算子の結合順について微妙な差異がありますよ。という話。

CとC++の演算子 - Wikipedia

ここにありますように、

	e = a ? b : c = d;

での扱いがCとC++で違うんですね。

いやはや...だれがこんな式書くんだろうwww

こんな式を書いたことないんだが、C言語だと通らないのかぁ。

なんとなく通りそうに見えたのになぁ。

意外だ、というだけのお話でございました。

...建前なげぇ...

通りすがり通りすがり 2012/10/31 15:12 >三項演算子は主に実行時定数の方に対して使う。

>当たり前であるが、コンパイル時定数であるものが実行時の処理なぞ必要とするはずがないのであるから三項演算子なぞ不要である。

とありますが、コンパイル時定数にも三項演算子を活用することができます。
もちろん三項演算子内で用いる値がすべてコンパイル時定数である必要がありますが、この手法を用いてsize_t等を引数にとるテンプレートの特殊化をせずに済んだりします。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/jokerfino/20091119/p1