C++のクラス宣言の文法について

C++ビギナー向けに書いた文章です。

C++の入門書では、クラスの書き方を次のように教えているものが多いと思います。

class MyClass
{
  // ...
};

int main()
{
  MyClass foo = new MyClass();
  // ...
}

クラス定義と呼び出しを分けて記載されています。 これ、初めて見た時に文法が覚えにくい気がしていました。 クラス定義の一番最後の「;」を忘れてコンパイルエラーになることもしばしば。

C#では、同じクラス定義でも

class MyClass
{
  // ...
}

と、一番最後の「;」は必要ありません。それどころか付けているとエラーになります。

C++ ではなぜこのような文法なんでしょうか。

答えは C++ の文法は C を踏襲しており、C にこそその本質があります。 C ではクラスは存在しないため、同じメンバを持つ入れ物である構造体の宣言文法と置き換えて説明します。

メンバを持つ型定義の基本形

C においても、多くの入門書では構造体の宣言を次のように複数行に分けて書くことが多いと思います。

struct {
  int x;
} foo;

これだと文法が分かりづらいため、一行にしてみましょう。

struct { int x; } foo;

はい!一行になりました。それぞれの変数名などをより一般的な用語に置き替えると次のようになります。

struct { メンバ定義 } 変数名;

さらにこの宣言文を抽象的に書くと

型 変数名;

という形に集約することができます。お気付きでしょうか?

int n;

などの通常の変数と同じ文法です。つまり、struct { メンバ定義 } の部分はただの「型」なのです。

struct { メンバ定義 } 変数名;
~~~~~~~~~~~~~~~~~~~~~
    ↑ ただの型

この形をよく覚えてください。これを基本形だと考えればいいでしょう。

コード例

基本形を用いたコードの例です。

int main()
{
  struct { int x; } foo;
  foo.x = 123;
  printf("x = %d\n", foo.x);
  return 0;
}

struct { int x; } の部分はまるごと型を表しているため、struct { int x; } 型の変数 foo が使われているのが分かると思います。

タグ名

さて、C の文法ではこの宣言した構造体に別名(タグ名)を付与することができます。

struct Hoge { int x; } foo;

Hoge の部分がタグ名です。この宣言以降は

struct Hoge bar;

という文法で、{ から } までの定義部分を書かずに用いることができます。

さらに、変数を宣言せずに型のみを宣言する場合は、

struct Hoge { int x; };

とし、変数名を省略します。

コード例

タグ名を用いたコードの例です。

int main()
{
  struct Hoge { int x; };
  struct Hoge foo;
  foo.x = 123;
  printf("x = %d\n", foo.x);
  return 0;
}

3行目で構造体の宣言と定義のみを行い、Hoge というタグをつけています。 4行目でさきほどつけたタグを元にして、変数 foo を宣言しています。

typedef

少しよりみちになりますが C は文法上、型名 struct Hogestruct を省略することができません。つまり、

struct Hoge foo;

は正しい変数宣言。

Hoge foo;

はあやまった変数宣言となります。そこでよく用いられるのが型名に別名を与える typedef です。

typedef int abc;

と書いておくと、それ以降は

abc n;

int n;

として解釈されます。一見、#define マクロのように名前が置換されただけのように見えますが、typedef はプリプロセッサレベルではなくコンパイラ(構文解析器)レベルで正しく「型」として認識されるため、より安全なコードを書くことができます。

さて、この typedef を用いて struct を書かずに済む宣言をしてみましょう。

typedef struct { int x; } Hoge;

先ほどの基本形と似ていますが、typedef の構文であるため、別名は末尾になります。(変数名と混同しないようにしましょう) これでコード中で

Hoge foo;

と書くと、

struct { int x; } foo;

として解釈されるようになります。 なお、C++では typedef を行わなくても struct を省略する事が可能です。

C++ で

C++では、C文法の上位互換がありますので、

struct Hoge { int x; };

という構造体宣言・定義がそのまま使えます。

この文法をそのままクラス定義にも用い、

class Hoge { int x; };

といった記法を行うわけです。末尾にちゃんと「;」がいますね。 構造体と同じ文法である以上、クラスも

class { int x; } foo;

という具合に無名クラス、変数宣言付きの記述も可能になっています。

C/C++の構造体・クラスの宣言は、ひとつの「文」として記述しているわけです。

C# では

C#ではCやC++に似た文法を採用しつつ、構文解析がより具体化しています。

class { int x; } foo;

のようにクラスの定義と変数の宣言を同時に行うことはできず、

class Hoge { int x; }

というクラス名・タグを持つ宣言のみに限定されています。 変数を宣言する必要がないという理由からか、末尾の「;」も付けません。C/C++ では「1文」だったものが、C# では「1節」になっています。

CやC++では煩雑になりがちな型の宣言や変数の定義を文法上制約して、できるだけすっきりとした構造にすることが目的だと考えられます。

ただ、C# では

int Main()
{
  class Hoge { int x; }
  // ...
}

という具合に、メソッド内でのみ利用したい「使い捨て」クラスを文法上定義することができなくなる弊害が出てしまいました。

これが実際に問題になっていたのか、C# 3.0 から「匿名型」という文法が導入されました。

var foo = new { x = 0 };

明示的にメンバの型を指定することができなかったり、型を決定するために初期値を指定しなければならなかったりと色々と残念な状態になっています。

また、

int x = 0;
var foo = new { x };

という初期化、型決定、メンバ名の決定というもはやカオスな機能を提供していたり、動的にヌルヌルしているかと思えば一方で

var foo = new { x = 0, y = 0 };
var bar = new { y = 0, x = 0 };
// foo と bar は宣言(定義?)順が違っているので別の型

という C の構造体かよ!と勢いよくツッコミを入れたくなる仕様だったりします。

文法という側面だけを見ても、ラムダ式や Func 型など、他にもいろいろと首をかしげる部分が多い言語です。 Java を駆逐し、Ruby や Python のような柔軟性を模索していると言えば聞こえがいいですが、他の言語に比べても後手後手にまわっている Microsoft の「商売戦略仕様」が見え隠れしているのは気持ち良いものではありません。

OpenCASCADE 6.9.0 を Debian でビルド

先月の12日にリリースされた Open CASCADE の 6.9.0 をビルドしました。公式のロードマップでは 6.x 系列は 6.8 が最後で、次は 7.0 になるとの話だったはずですが、何だかんだで 6.9 がリリースされた模様

v6.9 のリリースノートを読んではいましたが、ここのところ忙しくて対応ができなかったので出遅れてしまいました。

ここ数バージョンは、ビルドの仕方マニュアルがいろいろと整備されてきて、時代に合わせてなのか、ビルド環境ごとの Markdown なドキュメントも付属しています。このブログで改めて紹介する必要性もあまりなくなってきました。詳しい内容は公式ドキュメントに任せるとして、要点だけかい摘まんで紹介したいと思います。

公式ドキュメンテーション

v6.9.0 の内容は次のドキュメントで網羅されています。

v6.9.0 のリファレンスマニュアルは、ソースコード tar ボールや Windows インストーラー内に同梱されています。最も最新バージョン(開発版)のリファレンスマニュアルであればこちらで参照できます。

v6.9.0 リリースの要点

モデリング・アルゴリズム

ファジー ブール演算

これまでのバージョンでは、ソリッドモデル等で行うブール演算時に対象と対象がちょうど同一の位置にある面や線を共有して存在していた場合、意図した演算結果にならない事が多くありました。意図した結果にならない場合はまだ良かったんですが、いつまで経っても計算が帰ってこない、または例外を吐いてお亡くなりになるケースもありました。この点が改善されているようです。

複数の引数を取るブール演算

これまでのブール演算では TopoDS_Shape を二つ用いて、Fuse, Common, Cut 等の演算を実行していました。あんまり詳しく見てませんが、同時に複数の TopoDS_Shape を演算させることができるようになるようです。これまで数回の処理で書いていたものが一度にできるわけですが、複数のソリッド群の一つに reverse させたソリッドを混ぜて Common に投げて、結果的にその部分だけは Cut させる動作にするなど、より「ブーリアン」っぽい演算としてコーディングできるようになりそうです。

ビジュアライゼーション

選択処理を再設計して、より良いパフォーマンスに

OCCT のビジュアライゼーションは長いことほったらかしになっていた様子で、ちょっと貧弱でした。これは、あくまでも OCCT は三次元幾何演算とトポロジックな形状の演算がメインであり、代替ライブラリーを用いて解決できるビジュアライゼーションは二の次でも大丈夫!という背景もあります。 実際、OCCT を採用している有名なオープンソース三次元 CAD ソフトの FreeCAD は、OCCT 提供のビジュアライゼーションを使用せずに別のライブラリで三次元ビューを提供しています。 ただ、ここ数バージョンでは、前述の幾何を扱うことろがだいぶ安定してきたのか、二の次だったビジュライゼーションが次々と強化されており、パフォーマンスも改善しています。まだ実験的な印象ではあるものの、レイトレーシングレンダリングの採用、GLSLシェーダが読み込めるようになっていることなど、単なる計算ライブラリから本当に「CAD」として必要な機能の強化が見てとれます。 後述する VTK の採用も注目すべき点です。

OpenGL ES 2.0 互換

ビジュアライゼーションの改良の特記すべきもう一つの点は、iOS, Android などのモバイル端末への対応です。OpenGL ES はモバイル端末向けの OpenGL のサブセットです。OCCT の幾何ライブラリの部分は、早い段階から Android にも移植されて動作していましたが、ビジュアライゼーションはそのままではビルドできませんでした。ES 互換となることにより、これらの環境へもシームレスな移植が可能となりそうです。

シェイプを表示する際の三角形メッシュ化の制御

コンテキストにシェイプを追加する際、これまでは必要に応じて自動的にシェイプの描画データである三角形メッシュを内部で生成していました。これを外側から無効にするオプションが導入されたようです。 形状の変形はなく、大量のシェイプを回転させるだけでも再三角形メッシュ化が走って表示速度が落ちていましたが、それらを動的に制御することができそうです。(※回転も行列演算している意味では変形(トランスフォーメーション)ですが、自由曲面の式まで遡って、形状の三角形メッシュを再構築する必要すらない場合も往々にあります)

その他

Intel TBB ライブラリを使わない並列処理のサポート

こちらも詳しく追っていませんが、自前で並列処理を行う環境が整いつつあるようです。

サンプルに AndroidQt, JniViewer for Android が追加

Android 環境のサンプルも充実してきました。

JniViewer

JniViewer for Android

AndroidQt

GUI は QML を使って書いてあるみたいです。本当に WPF/.NET Framework なんて使ってる場合じゃないです。

AndroidQt

その他のサンプルに、Qtデスクトップ版、MFC、C# があります。C# では WPF ベースのものと System.Windows.Forms 版があり、今のご時世でいうと半ばどうでもいいですが、Direct3D デモも追加されています。

v6.9.0 の全体的な感想

幾何演算部分の改良は喜ばしい限りで、ビジュアライゼーションの強化も頑張ってほしいところです。ようやくモバイル端末のサポートも視野に入れてきているようですが、個人的にはいっそのこと三次元ビューアは WebGL 環境へシフトしていくのもアリなんじゃないかなぁと考えています。OCCT をサーバ上で並列処理させて、クライアントの WWW ブラウザ上で表現する…素晴しいじゃないですか。最近は NW.js (Node.js + Webkit)のようなツールキットも存在しますので、ライトウェイトなビューアーからワークステーションで動かす CAD まで環境に依存しないビジュアライゼーション機能になっていけばいいな、と思っています。え?お前が Context や Viewer, View を JavaScript で再実装しろって?それが仕事になればいいんだけどなぁ…;)

v.6.9.0 のビルド

ビルド方法は次のドキュメントに詳しく書いてあります。

なお、このドキュメントはソースコード tar ボールを解凍して生成されるdoxディレクトリ以下に markdown ファイルとして収録されています。(逆に言うと、上記の HTML はそれらのファイルを doxygen にかけたものと思います。)

ビルド環境

OCCT のビルド環境もかなり充実してきており、以前のようにチューニングしながらあれやこれやする必要もほとんどなくなりました。公式サポートしているのは次の環境です。

  • Automake
  • CMake
  • CMake + ADT (Android SDK)
  • Code::Blocks (Mac OSX)
  • Xcode (Mac OSX)
  • Microsoft Visual C++ (Windows)

また、パフォーマンスチューニングレポートでは Linux 版の Clang/LLVM でも検証されているようです。

今回は一番馴染みがある Automake でいきたいと思います。

ビルド手順

作業ディレクトリに入り、tar ボールを落としてきます。ソースコードは前バージョンまではユーザー登録&ログインしないとアクセスできなかった気がしますが、今回から下記の URL で落とせるようです。なお、git での clone は登録が必須です。

cd /tmp
wget http://files.opencascade.com/OCCT/OCC_6.9.0_release/opencascade-6.9.0.tgz

解凍してディレクトリにはいります。

tar zxvf opencascade-6.9.0.tgz
cd opencascade-6.9.0/

configue をビルドして configure します。オプションについては後述します。

./build_configure
./configure -prefix=/opt/occ690 \
-with-vtk-include=/usr/include/vtk-6.1 \
-with-vtk-library=/usr/lib/i386-linux-gnu/

make して install します。

sudo make -j2 install

とりあえずこれだけです。便利。

貧弱な自宅サーバ(Debian GNU/Linux、32bit環境、Intel Atom)では、make で

real    183m57.121s
user    336m24.168s
sys     16m36.896s

となりました。

configue オプション

-prefix=

最終的にビルドしたファイルをインストールする場所です。自分は /opt/occ690 に指定しています。

-with--include=, -with--library=

ヘッダのインクルードパスとライブラリパスを追記しています。VTK が見えてなかったようなので、明示的に指定しました。

その他のオプションはこちらを参考にしてください。

ビルド時のテクニック

configure では、ビルドする環境にインストールされているライブラリの有無によって OCCT のモジュールとよばれるライブラリ群の Makefile を生成します。

モジュール > ツールキット > パッケージ > クラスや列挙型など

OCCT は上記のように階層構造を持ったライブラリです。ツールキットの部分が共有ライブラリファイル(.so)に当たります。Windows ではダイナミックリンクライブラリ(.dll)になります。モジュールはそれらをまとめて用途別にグループにしたものだと思ってください。

モジュールには次のものがあります。

  • FoundationClasses … 共通関数などを提供
  • ModelingData … 幾何形状をデータとして表現する機能
  • ModelingAlgorithms … 幾何形状を構築する機能
  • Visualization … 幾何形状を画面に描画する機能
  • ApplicationFramework … OCCT を使ったアプリのための有用な機能
  • DataExchange … 幾何データファイルのサポート(IGES,STEP,STLなど)
  • Draw … テスト環境

ざっくりと説明すると上に行くほど依存関係が少なくてすみます。詳細な依存関係はリファレンスマニュアルの図のとおりです。

occt modules

さらに、個々のモジュール内のツールキットの中で依存関係があります。なお、IGES などのデータファイルをサポートするには、GUI が必要がない場合でもビジュアライゼーションやアプリケーションフレームワークの各ツールキットを全て背負い込まなければならないかというと、実はそうではありません。具体的にはモジュール同士の依存関係の奥にはツールキット同士の依存関係があり、それを満たしていればこの図の限りではありません。今のところ、モジュールを越えた関係図はないので、リファレンスマニュアルを見て何が必要なのかを調べる必要があります。

さて、ビジュアライゼーション以下のモジュールは、さらに多くの別の人(サードパーティ)が開発しているグラフィック系のライブラリに依存しています。

そのため、グラフィック系のライブラリがない環境で configue すると、ビジュアライゼーション以下のモジュールが無効になった Makefile が生成されます。

configure の出力に各モジュール名と Yes/No といった表示が出力されるので、それでモジュールが有効になっているか無効になっているか、無効の場合はどのライブラリが足りていないのかを表示してくれます。以下に私の環境の configure 出力を示します。

3rdparty mandatory products       
---------------------------------
freetype      : yes 
tcltk         : yes 

3rdparty optional products       
---------------------------------
gl2ps         : no (--with-gl2ps=DIR option was not defined)
freeimage     : no (--with-freeimage=DIR option was not defined)
tbb includes  : no (--with-tbb-include=DIR option was not defined)
tbb libraries : no (--with-tbb-library=DIR option was not defined)
opencl        : no 
qt            : no (--with-qt=DIR option was not defined)
vtk           : yes 

Component                   Build
--------------------------  -----
FoundationClasses           yes 
ModelingData                yes 
ModelingAlgorithms          yes 
Visualization               yes 
ApplicationFramework        yes 
DataExchange                yes 
Draw                        yes 

「3rdparty mandatory products」は必須ライブラリ、「optional」は指定があったら使うライブラリです。必要なライブラリとそのバージョンは、以下にまとめてあります。

Open CASCADE Technology: Overview

Debian GNU/Linux jessie 32bit 版では、すべてのサードパーティ・ライブラリを標準リポジトリからインストールすることができました。(ソースコードの取得とビルドは不要)

また、テスト環境である Draw や Qt のサンプルプロジェクトをビルドする必要がなければ、 Tcl/Tk や Qt のインストールは必要ありません。 上記の「mondatory products」に Tcl/Tk が入っているのは、すべてのモジュールをビルドする際に必須、という意味だと思います。

siren をビルド

OCCT を使った siren というソフトを作ってます。OCCT が提供する三次元幾何演算機能を Ruby で簡単に記述することができ、ちょっとした演算なら手軽にすることができます。

上記でビルドした OCCT 6.9.0 を使って siren の最新リビジョン(OCCT6.8.0ベースで記述)をビルドし直してみると、あっけなくビルドが通りました。クラス名が変更されたり、siren で使っている機能が変更されることがなかったようです。

ひとまず安心しました。


ASP.NET を初体験

Web アプリケーションといえばもっぱら Perl, Ruby, Python あと少しだけ PHP な自分なのですが、仕事でちょっとした ASP ベースの API を書かなければならなくなりました。うちは90年代に育った小さな会社ということもあり、ネットワークは Windows ドメインネットワーク、Web サーバーも IIS ベースという(自分にとっては)息が詰まるような環境です。

ASP が書けるプログラマーがほとんどいない状況で、ASP ベースの API を担当していた先輩が事故で入院してしまい、その後釜を任された同僚のサポートという形で扱うことになりました。元々の API は無印の ASP ですので、いわゆる Visual Basic 6 的な構文。仕事の内容なので、あまり具体的には書きませんが、90年代半ばの機能を駆使してわざわざ面倒かつ煩雑なシステムにしてしまっているように思えて仕方ありません。UNIX における枯れた技術と Microsoft の政略が絡んだ枯れた技術では意味が違うと思いました・・・。

IIS の意味が分からない

Apache, lighttpd, nginx などの Web サーバーの考え方がベースになっている自分にとって、Microsoft の Web サーバー Internet Information Service の意味が分かりませんでした。実装した API の不具合がスクリプトサイドにあるのか、Web サーバーにあるのか、DB サーバーにあるのか、構造から理解するのに苦しみました。UNIX 生まれのインターネット技術をパクった挙句、ユーザーのためではなく自分らのために無駄なものをたくさん乗っけて、挙句、複雑怪奇な環境を提供しているようにしか思えません。Microsoft が出している開発環境をお金を出して買えば、トータルで面倒を見てくれるように商売戦略上設計されているんでしょうが、非 Web 環境で毎日 Visual Studio を使っている UNIX ユーザーとしてみれば、それも眉唾ものだと思いました。

Visual Studio の意味が分からない

Microsoft が提供している環境というのは往々にして、「ユーザーが自分でモノを考えて何かをやろうとすると墓穴を掘る」みたいなので、大人しく Visual Studio の Web デベロッパーツールを使用することにしました。うちは VS2008 ベースですので必然的に ASP.NET になります。Microsoft の技術にコードを投入してしまうと、過去の遺産とかそういうものは諦めないといけません。 VS2008 をインストールした時点で、まさか ASP.NET をすることになるとは思ってもいなかったので、追加インストールが必要になりました。追加インストールのためインストーラを起動してみると、嫌がらせのようにインストーラがエラーで落ちます。私の環境には、VS2010 と 2013 も入っているのですが、それが影響しているんでしょうか・・・。調べる手口もありません。 オープンソースなソフトウェアならば、調べてバグレポートのひとつも送らねば!と思うのですが、お金を払った上にこれだとそんなにマゾにもなれません。さっさと諦めてしまいました。

オープンソースの .NET 環境 Mono が素晴しい

平日、家に帰った後と休日はもっぱら Linux ベースで作業しています。職場では Windows と Visual Studio を使っています。どちらも時間的には半々なので、自身では盲目的な信者のつもりはないのですが、やっぱりオープンソースの方がユーザーフレンドリーです。

自宅の Linux 環境にも Mono を入れているので、もしかしたら ASP.NET も Linux 上で上手い具合に開発ができるのかなーなんて思って調べてみると、やはりありました。オープンソース万歳!信者とでも何とでも言ってくれ!

aspnet_on_monodevelop

Mono Develop にも最初から ASP.NET ソリューションを作成するのがついてました。もう「これでいいじゃん」感がハンパないです。さて、実際に開発をするには ASP.NET を動かすための Web サーバーを導入する必要がありますが、これも Mono 開発環境の一つとして XPS サーバーが準備されています。こいつも例のごとく、コマンド一つで導入できるのがクールです。

sudo aptitude install mono-xsp

Mono Develop のサンプルの ASP.NET ソリューションをデバッグ実行すると、次のようにちゃんと動きました。ハラショー。

asp_net_running

互換性についても C# は確か ECMA だか ISO だかで規格化されていますし、Visual Studio や IIS といった環境に左右されまくる Windows ベースでの開発よりもよっぽど安心して出来そうです。ちなみに、うちの環境では .NET は 4.0、C# は ISO-2 までサポートされています。

mono40

MonoDevelop が Vi キーバインドできるようになって、C++/CLI を使わずに済むプロジェクトをやるようになったら、本気で MonoDevelop で仕事しようと思いました。

追記

デフォルトで Vi キーバインドできました。快適すぎて涙が出てきそうです。

.NETで正規表現ライブラリを使う

前回のboostの正規表現ライブラリのサンプルに引き続き、C#とC++/CLI、VB.NETでも同じサンプルを書いてみました。全て.NET FrameworkのSystem.Text.RegularExpressions.Regexを用いています。

なんてことはありませんね。

C#版

こちらはMono 2.6.7環境のmcs/gmcsでビルドしてチェックしました。

[csharp]
using System;
using System.Text.RegularExpressions;

// usage: a.exe pattern replace-to

public class Program
{
static int Main(string[] v)
{
if (v.Length != 2) return 1;

Regex r = new Regex(v[0]); // 正規表現オブジェクト
string buf;                // 読み取り用のバッファ

while ((buf = Console.ReadLine()) != null)
  Console.WriteLine(r.Replace(buf, v[1]));

return 0;

}
}
[/csharp]

C++/CLI版

C++/CLIはオープンソースなコンパイラが存在しないため、Microsoft Visual Studio 2008 SP1を用いてビルド、チェックしました。

[cpp]
using namespace System;
using namespace System::Text::RegularExpressions;

// usage: a.exe pattern replace-to

int main(array<System::String ^> ^v)
{
if (v->Length != 2) return 1;

Regex^ r = gcnew Regex(v[0]); // 正規表現オブジェクト
String^ buf;                  // 読み取り用のバッファ

while ((buf = Console::ReadLine()) != nullptr)
    Console::WriteLine(r-&gt;Replace(buf, v[1]));

return 0;

}
[/cpp]

VB.NET版

ついでにVB.NET版も。これもMonoのvbncでテストしました。

[vb]
Imports System
Imports System.Text.RegularExpressions

Module Program

Public Sub Main(ByVal v() As String)

If v.Length &lt;&gt; 2 Then Exit Sub

Dim r As New Regex(v(0)) ' 正規表現オブジェクト
Dim buf As String        ' 読み取り用のバッファ
buf = Console.ReadLine() ' 代入結合判定できないので
                         ' 予め内容を詰めておく。

While buf &lt;&gt; Nothing
  Console.WriteLine(r.Replace(buf, v(1)))
  buf = Console.ReadLine()
End While

End Sub

End Module
[/vb]