meryngii.neta

今日も新たな"ネタ"を求めて。

volatileとコピー・代入

C++でvolatile修飾された構造体はものすごく扱いにくい。

typedef struct { int x; } X;

int main()
{
	X n_1;
	volatile X v_1;
	
	/* コピー(代入ではない)*/
	X n_2 = n_1; /* OK */
	X n_3 = v_1; /* エラー */
	volatile X v_2 = n_1; /* OK */
	volatile X v_3 = v_1; /* エラー */
	
	/* 代入 */
	n_2 = n_1; /* OK */
	n_3 = v_1; /* エラー */
	v_2 = n_1; /* エラー */
	v_3 = v_1; /* エラー */
	
	return 0;
}

このコードはC++では大量のエラーが出るが、C言語ではコンパイルが通る。おそらくCとC++の非互換性の一つではないかと思われる。
原因はC++のデフォルトコピーコンストラクタ・代入演算子の定義にある。

X::X(const X&)
X& X::operator=(const X&)

これらの関数の引数になったりthisポインタになったりするときに、volatileがついていると暗黙の型変換ができないのでエラーとなる。
これはC言語の型変換が甘いことや、C++の型チェックが厳しすぎることが原因ではない。C++のデフォルトの関数が足りないことが原因である。
一番望ましい対策は標準でこれらを定義することだと思う。

X::X(const volatile X&)
volatile X& X::operator=(const volatile X&) volatile
X& X::operator=(const volatile X&)
volatile X& X::operator=(const X&) volatile

パフォーマンスを考慮しないなら下の二つは必要ない。
volatileはあまり使われていないのだろう。*1この件に関して見つかったのは2chのレスぐらいだった。*2しかしC++C言語との互換性を重視しているはずなので、ぜひとも検討してほしい。
ちなみに、volatileが絡む例で一つだけコンパイルが通っている行がある。

volatile X v_2 = n_1; /* OK */

v_2はvolatileオブジェクトだが、構築中であるのでエラーにはならない。コンストラクタでthisポインタが修飾されることはないので、例えば次のようなコンストラクタは定義できない。

X::X() volatile { } // エラー

さて、ユーザレベルで対応するにはキャストするのが手っ取り早い。

template <class T>
inline void Assign(volatile T& dest, const volatile T& src)
{
	const_cast<T&>(dest) = const_cast<const T&>(src);
}

結果は保証されないだろうが、現実的にはあまり問題は無いと思う。

*1:組み込みでは大量にvolatileをばらまくことがよくある。

*2:http://pc11.2ch.net/test/read.cgi/tech/1234420483/182 海外のコミュニティにはありそうな気がする。