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

投稿者: | 2015年9月23日

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 の「商売戦略仕様」が見え隠れしているのは気持ち良いものではありません。

コメントを残す

メールアドレスが公開されることはありません。