並ゲームプログラマの書き捨て場

ゲームプログラマやってる私の日記、雑記、備忘録、公開場所

しょうもない個人的騒動の話【C++】

So... Do you know? Visual Studio is defective compiler. Because very many bugs exist.
・・・djannです。

英語なんて分からないので、適当に書いている。英語が得意な人は、ぜひ教えてくれると嬉しい。


初っ端からVisual Studioの事をディスってしまっているが、それはあくまでC++に関しての事だけで、単純な開発環境としては割と評価しているので誤解しないでほしい。ただC++C++じゃないというだけの話である。

さて、ではそのC++の部分でどのような騒動に遭遇し「そう・・・どうしようか」となったのかを書いていこう。なお、この記事のターゲットは、現在仕事でメインに使うことになっているVisual Studio 2012となる。そしてここに書いた事は、多分もう大体周知の事実となっていることだろう。

C++11対応の貧弱さ。

 これはもう、言わずもがなと言ったところだろう。一体いくつのworkaroundを仕込んだか分からない。やりたい事を簡単に実現する為の下準備中、当たり前のように書いたコードが当たり前のようにエラーになってくれる悲しみは何とも言い難い。

よもやマクロ展開をループ(再帰)させるコードを私が書くことになるとは思いもよらなかった。可変長引数テンプレートが恋しい。

またconversion to unspecified bool idiomをこの時代になって一々書くとも思っていなかった。explicitの一文が中々どうして、大きな影響を与えてくれていた。

何気に困ったのが、関数テンプレートへのデフォルト引数が存在しない事でもあった。普通に初期値として使うだけでなくSFINAEに便利に使わせてもらっていた。

他キーワードとしてconstexprは空欄へ、noexceptはtrue, falseを受け取れない形でthrow()へ置き換えられるよう定義、alignas(x)は__declspec(align(x))へ、alignof(x)は__alignof(x)へと置き換えた。
が、alignasやalignofはVisual Studioの拡張である__declspec等がテンプレート引数や変数をそのまま受け取ってくれなかったので、そのまま代用とはならなかった。

正しい関数が呼ばれないバグ

 明確にバグだ!と断定できるほどC++の規格に詳しい訳ではないが、若干おかしい挙動を示してくれるコードが存在した。以下のコードを見てほしい。

#include <iostream>


struct foo {
	int x;
	foo() : x( 10 ){
		std::cout << "def constructor : " << x << std::endl;
	}
	template <class vType>
	foo( vType other ){
		std::cout << "constructor ";
		std::cout << "this is " << this << std::endl;
		std::cout << "x is " << x << std::endl;
		x = 20;
	}
	template <class vType>
	foo &operator = ( vType rhs ){
		std::cout << "operator ";
		std::cout << "this is " << this << std::endl;
		std::cout << "x is " << x << std::endl;
		x = 30;
		return *this;
	}
};

void func(){}

int main(){
	foo f;
	f = func;
	f = func;

	std::cout << "f.x is " << f.x << std::endl;
}

このコードの出力はどうなると思うだろうか?私が最初に予想したのはこうだった。アドレスは適当だ。

def constructor : 10
operator this is 0074FBAC
x is 10
operator this is 0074FBAC
x is 30
f.x is 30

だが、Visual Studio 2012付属のコンパイラコンパイルされた結果は、以下のような出力を出してきた。

def constructor : 10
constructor this is 0074FBAC
x is -858993460
constructor this is 0074FBB8
x is -858993460
f.x is 20

代入にも関わらず、謎のオブジェクトが生成されテンプレートコンストラクタが呼ばれている。f.xには代入で入っていて欲しい値が正しく入っていない。
ちなみにこれは、f = funcではなくf = &funcと書くことで回避され、予想と同じ結果を出力してくれた(Clangでも同様)。これから関数(メンバ関数)を扱う際には、必ず明示的に&を付けるようにした方が安全なのかもしれない。

なお、template <class vType>とあったとして、引数を受け取り、そのvTypeをsizeof(vType)とすることで、&を付けずに代入しようとしたものをコンパイルエラーにすることが出来るので、そういう作りにして使用者にも強制するようにしている。
ちなみにVisual Studio 2012ではこのコンパイルエラーがコンストラクタの部分で働くので、最適化等で挙動が変わってしまったというより、完全にパースの時点で間違っていると思われる。

※補足としてだが、f = funcならvType = void (void)という型に推論され、f = &funcとした場合はvType = void (*)(void)と推論されているようだ。

コンパイルすら出来ないテンプレート

 VC++の倒し方知ってる?俺は知ってるよ。

template <class vType, int vType::* = nullptr> struct bar;

Visual Studio、お前はしぬ・・・!とばかりに、内部エラーが発生してはじけ飛んでくれる。これはRead Onlyのプロパティでも作ろうとした際に遭遇したコードを最小化したものである。

ちなみにこのコードはVisual Studio 2008、Visual Studio 2010、Visual Studio 2012の三種で試し、どれも同様に内部エラーが発生してコンパイルが出来ない。2013や2015はまだ試していないので、今度暇があったら・・・少なくとも2013は試しておこうと思う。

必ずメモリをリークする標準ライブラリ

 いわずもがな、Visual Studio 2012のstd::threadの事である。これは、使用してスレッドを一度でも起動すると、必ず44 Byteほどメモリをリークする。
for ( int i = 0; i < 10000; ++i ){ std::thread th( foo ); th.detach(); }
とこうしても44 Byteである。使用する回数やオブジェクトの数などは関係ないようなので、ある意味安心して使っても良いのかもしれない。良くないのかもしれない。


余談だが、Clang for Windowsを用いる時は、私はプロジェクト設定に_HAS_EXCEPTIONS=0と入れている。つまりは#define _HAS_EXCEPTIONS (0)と書いたようなものだ。これにより、未だ対応出来ていない例外をスルーしてClang for WindowsでもSTLが使えるようになる。
だが少なくともVisual Studio 2012上で、これを定義したままstd::threadを使おうとすると、__uncaught_exceptionという識別子が定義されていないとのことでコンパイルエラーが発生してしまう。せっかくなので似た名前のstd::uncaught_exceptionに置き換えるよう定義してやると、正常に(?)動いた。Clang for Windowsを用いる際には必須かもしれない。





他にもmin, max, near, farマクロ等(Visual StudioというよりはWinAPIのヘッダ)で健全なコードが汚されたり等あるが、ひとまずこの辺で終わらせておく。
早く全人類がC++11、いやC++14を当たり前のように使える世の中になりますように。