国際標準規格の紹介 〜ISO国際標準規格プログラミング言語C++について〜
はじめに
こんにちは、C++標準会員の江添です。
今日は普段から表に出ている、閉鎖的なゲーム開発では使われていない開発環境についての紹介をします。
今回紹介するのは、ISO国際標準規格プログラミング言語の「C++」です。
なんと表の世界では、国際標準のプログラミング言語は、コンパイラーベンダーやC++利用企業と行ったC++に利害関係を持つ多数の代表が国際標準規格を制定しているのです。当たり前でしょ?
C++は以下のような特徴を持っています。
- エラーに気づきやすい静的型付け言語
- C++経験者が理解しやすい言語仕様
- OSに依存しない移植性の高いC++実装
- 実行環境に依存しないC++規格準拠の挙動の定義
- C/C++の関数・クラスに最初からバインドされている
今でもクローズな実装の詳細のわからないプログラミング言語がたくさんあり、それらを使うデメリットは数多くあります。
国際規格の言語を使うのは様々な利点があるからです。
一番大きなものは「権力者の慈悲にすがるしかない状況が発生しない」ことです。もし特定の個人や団体が言語を所有している場合、将来の変更が既存のコードを壊さないこと、自分のほしい新機能が追加されることは、権力者の慈悲にすがるしかありません。ISO傘下の国際規格として制定されることで、企業や個人は等しく最適な言語仕様を考えることができます。
今回はその中の一つとしてCRTPという技法を紹介します。
CRTP
自分の型を返す関数ってよくありますが、面倒くさいですよね。
class A_Class
{
public :
static auto Create()
{
return std::make_unique<A_CLass>() ;
}
} ;
CRTPを使うと、先程のコードを以下のように書くことができます
template < typename T >
struct factory_mixin
{
static auto Create()
{
return std::make_unique<T>() ;
}
} ;
class A_Class
: public factory_mixin<A_Class>
{ } ;
もう少し複雑な例を挙げます。これと似たような(しかもコピーする必要がある)クラスを、名前を変えながら数十、数百と増やしていくことを想像してみてください。
class A_VeryLongClassNameMadeByEzoe
{
public :
static auto Create()
{
return std::make_unique<A_VeryLongClassNameMadeByEzoe>() ;
}
static auto CreateWithTwo()
{
return std::make_unique<A_VeryLongClassNameMadeByEzoe>(2) ;
}
static auto Create( int value )
{
return std::make_unique<A_VeryLongClassNameMadeByEzoe>(value) ;
}
A_VeryLongClassNameMadeByEzoe() { }
A_VeryLongClassNameMadeByEzoe( int value )
: value(value) { }
private :
int value = 0 ;
} ;
CRTP実装は簡単ですね。
template < typename T >
struct factory_mixin
{
template < typename ... Types >
static auto Create( Types && ... args )
{
return std::make_unique<T>( std::forward<Types>(args)...) ;
}
static auto CreateWithTwo( )
{
// T::T(int)を持たない型は除外
constexpr bool b = requires { T(2) ; } ;
static_assert( b, "Type doesn't have constrcutor T::T(int)." ) ;
if constexpr ( b )
{
return Create(2) ;
}
}
} ;
CRTPがあるとゲームでよく使われる状態遷移モデルのような仕組みを描くのが大変楽になります。
現場では以下のようなクラスがたくさん作られるため、面倒なコードの重複によるバグを減らす(これが大切)ことができるのです。
template < typename T >
struct change_state_mixin
{
static void ChangeState( Obj aObj )
{
T newState( aObj )
aObj.stateChanger().setNextStage(newState) ;
}
} ;
// 待機状態
struct StateWait
: change_state_mixin<StateWait>
{
void procAnim()
{
// 状態ごとに違う処理
}
}
// プログラマーにコピペを強いる状態
struct StateForceHallabProgrammerToCopyPasteTheBoilarPlateCode
: change_state_mixin<StateForceHallabProgrammerToCopyPasteTheBoilarPlateCode>
{
void procAnim()
{
// コピペ処理
yank() ;
jjjjjjjjjjjjjjjjjjjjjjj() ;
paste() ;
enter_insert_mode() ;
}
}
まとめ
わざわざ閉鎖的な環境で独自言語を開発しなくても、ライブラリ作者向けの強力な機能が提供されているのがC++の強みです。時代とともに変化する様々な環境にも、C++は対応し続けています。
これ以外にも、C++ではボイラープレートコードを削減するために様々な機能がありますが、それはまたの機会にぜひ。
国際標準規格C++は、C++に利害関係を持つ様々な個人や企業がC++標準化委員会に代表を送り、ISOのルールに則って新機能の考案や策定を進めています。もし現行のC++に不満があるのであれば、C++標準化委員会に代表を送って提案し、実験的実装をし、その価値を示し、国際会議で議論して、規格策定に貢献しましょう。
真面目なまとめ
元ネタは以下の通り。
社内開発環境の紹介 ~社内製プログラミング言語Mintについて~ | ハル研ブログ | ハル研究所
元ネタで対処しようとしている問題は、コードをコピペするから生じるのであって、コードをコピペしなければよい。コードの重複を防ぐためには、昔から基本クラスという便利な機能がある。これにC++のテンプレートを組み合わせればボイラープレートコードをコピペして一部変更という不毛な処理を人間がする必要はない。そして、コンパイル時に解決できる問題はコンパイル時メタプログラミングで解決できる。
そして、C++の言語機能に不満があるのであればC++標準化委員会に出てきて議論するべきだ。例えばC++17にはメモリ解放処理で何もしない、単なるポインターを加算して返すだけのメモリ確保ライブラリのモノトニックバッファーリソースが入った。C++20には、ソート済みの連続したストレージで実装された連想コンテナーのflat_map/flat_setや、歯抜けを許す連続したストレージで実装したvectorのようだが中間要素の削除が早いcolonyなど、コルーチン、スタックフルコンテキストスイッチライブラリなど、ゲーム用途にも便利な新機能がたくさん提案されている。他にも、this_typeで解決したい問題にも適用できる、静的リフレクション機能も活発に議論されている。C++を活用するのであればC++に貢献すべきだ。