Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

C++の名前空間と前方宣言について

スポンサードリンク

f:id:fa11enprince:20150731120925j:plain
最初にざっくり説明すると、C++の名前空間はC#にある名前空間と同じ概念です。Javaでいうとpackageに近い概念です。
前方宣言(forward declaration)はC++特有のものだとおもわれます。

前方宣言について

あるクラスで包含(コンポジション: Composition)をしたときに、つまりとある別のクラスを利用したいときに、そのクラスのメンバーとして持つときに使うものです。言葉で説明するとちょっとわかりにくいですが、こんな感じです。よく皆さんやってるかと思います。
あんまりいい例が思い浮かばなかったんで2DのRPGの地図上のキャラクターのクラスを例とします。

Character.h

#pragma once
#include "Image.h"

class Character {
public:
    int     getHp() const;
    void    setHp(int hp);
    void    walk();
    // 他にたくさん…
private:
    int     hp_;
    Image   image_;      // ←これが包含
    // 他にたくさん…
};

※Image.hは同じ階層にあって、名前空間なしとする。
ただし、上記の例だと、Image.hをインクルードしないといけなくコンパイルがーーーといった問題が起きます。なので、伝統的な書き方では上記のコードは下記のようになります。

Character.h

#pragma once

class Image;
class Character {
public:
    int     getHp() const;
    void    setHp(int hp);
    void    walk();
    // 他にたくさん…
private:
    int     hp_;
    Image   *image_;      // ←ポインタにした
    // 他にたくさん…
};

ポインタであればクラスの宣言だけでよく、includeが不要になります。どんなクラス構造をしているかは教えなくていいってことになります。

名前空間について

Javaのpackageのようにディレクトリ構造とpackage名が一致している必要はなく、
自由に名前空間を作れます。ちょっと調べれば簡単に理解できる内容かと思われので割愛します。
サービス終了のお知らせ
詳細は上記を参照してください。

名前空間と前方宣言について

例えば、通常のC++の.hファイルと.cppファイルでクラスを分けるスタイルの場合で、
名前空間を使うとこんな感じです。
コンポジションで使っているImageクラスはGraphicsという名前空間にあるとします。
CharacterクラスはMapという名前空間のなかに置きます。

Character.h

#pragma once
namespace Graphics {
class Image;
}
namespace Map {

class Character {
public:
    int     getHp() const;
    void    setHp(int hp);
    void    walk();
    // 他にたくさん…
private:
    int     hp_;
    Graphics::Image* image_;
    // 他にたくさん…
};

}

Character.cpp

#include "Character.h"

namespace Map {

int Character::getHp() const
{
   return hp_;
}

void Character::setHp(int hp)
{
    hp_ = hp;
}

void Character::walk()
{
    // なんかいろいろやって
    // たいていコンストラクタとかで
    // imageはインスタンスの実体が作られているとする
    image_->drawPicture();
    // 省略
}

}    // 名前空間終わり

Character.h 間違い例

上記例を下記のように書きかえることはできません。

#pragma once
// 前方宣言でこのように書くのは許されていない
class Graphics::Image;
namespace Map {

class Character {
public:
    int     getHp() const;
    void    setHp(int hp);
    void    walk();
    // 他にたくさん…
private:
    int     hp_;
    Graphics::Image* image_;
    // 他にたくさん…
};

}

上記だとコンパイルエラーになります。
説明が下手なので、困った時の頼みの綱、stackoverflowの記事を一部訳しておきます(ピンボケした質問とか回答を訳すのはツラい)。

なんで名前空間で前方宣言をこんなふうにできないの?

class Namespace::Class;

っていうのをこうかかなきゃいけないの?

namespace Namespace {
    class Class;
}

VC++ 8.0だとコンパイラが

error C2653: 'Namespace' : is not a class or namespace name

っていうんだけどさ。(※日本語だと「error C2653: 'Namespace' : 識別子がクラス名でも名前空間名でもありません。」)
ここでの問題はコンパイラが(名前空間の)Namespaceがクラスか名前空間か判断できないのが問題だと思うんだけど、でもなんで、単なる前方宣言の時点からこれが問題になるの?(以下略)

回答

正しい答えが出てるじゃんか。ちょっと言い換えてみよう。
「こう書かなきゃいけない」のってのは、

Namespace::Class

っていう語(ターム)がコンパイラに
君が言うように「Namespaceって名前が付けられた名前空間を探して、その中でClassと名前の付けられたクラスを参照しているものを探す」
って教えてるからそうしなきゃいけないんだ。
だけど、コンパイラは君のいっていることをわかってない。だってコンパイラはNamespaceと名前のついた名前空間を知らないんだから。
たとえNamespaceという名前の付いた名前空間が

namespace Namespace
{
};

class Namespace::Class;

のようにあったとしてもだ。
これでもまだ動かない。なんでかって、その名前空間の外から
名前空間の範囲でクラスを宣言できないからだ。
名前空間の中にないとダメだ。
というわけで、実際は前方宣言はこーゆーふうに書ける。

namespace Namespace
{
    class Class;
}

参考

c++ - Why can't I forward-declare a class in a namespace using double colons? - Stack Overflow

なんか訳してて通販の吹き替え思い出しました(;´・ω・)妙なフレンドリー口調というか…。翻訳はパワー使いますね…。