Looking for c++ Answers? Try Ask4KnowledgeBase
Looking for c++ Keywords? Try Ask4Keywords

C++ ネストされたクラス/構造


classまたはstructは、自身の中に別のclass / struct定義を含むこともできます。これは "入れ子になったクラス"と呼ばれます。この状況では、包含クラスは「包含クラス」と呼ばれます。ネストされたクラス定義は、囲むクラスのメンバーであると見なされますが、それ以外の場合は別です。

struct Outer {
    struct Inner { };
};

囲むクラスの外側からは、スコープ演算子を使用してネストされたクラスにアクセスします。ただし、囲みクラスの内側からは、修飾子なしで入れ子クラスを使用できます。

struct Outer {
    struct Inner { };

    Inner in;
};

// ...

Outer o;
Outer::Inner i = o.in;

ネストされていないclass / struct同様に、メンバー関数と静的変数は、ネストされたクラス内またはその囲む名前空間内で定義できます。ただし、ネストされたクラスとは異なるクラスと見なされるため、それらを囲むクラス内で定義することはできません。

// Bad.
struct Outer {
    struct Inner {
        void do_something();
    };

    void Inner::do_something() {}
};


// Good.
struct Outer {
    struct Inner {
        void do_something();
    };

};

void Outer::Inner::do_something() {}

ネストされていないクラスと同様に、ネストされたクラスは、直接使用される前に定義されていれば、後で宣言して定義することができます。

class Outer {
    class Inner1;
    class Inner2;

    class Inner1 {};

    Inner1 in1;
    Inner2* in2p;

  public:
    Outer();
    ~Outer();
};

class Outer::Inner2 {};

Outer::Outer() : in1(Inner1()), in2p(new Inner2) {}
Outer::~Outer() {
    if (in2p) { delete in2p; }
}

C ++ 11

C ++ 11より前では、ネストされたクラスは、囲むクラスからの型名、 staticメンバー、および列挙子にしかアクセスできませんでした。囲むクラスに定義されている他のすべてのメンバーは制限を受けませんでした。

C ++ 11

C ++ 11では、ネストされたクラスおよびそのメンバーは、それらが囲むクラスのfriendかのように扱われ、通常のアクセス規則に従ってすべてのメンバーにアクセスできます。ネストされたクラスのメンバーが、囲むクラスの1つ以上の非静的メンバーを評価する機能を必要とする場合は、インスタンスを渡す必要があります。

class Outer {
    struct Inner {
        int get_sizeof_x() {
            return sizeof(x); // Legal (C++11): x is unevaluated, so no instance is required.
        }

        int get_x() {
            return x; // Illegal: Can't access non-static member without an instance.
        }

        int get_x(Outer& o) {
            return o.x; // Legal (C++11): As a member of Outer, Inner can access private members.
        }
    };

    int x;
};

逆に、囲むクラスは、入れ子になったクラスのフレンドとして扱われていないので、明示的に許可を付与されることなく、そのプライベートメンバーにアクセスすることはできません。

class Outer {
    class Inner {
        // friend class Outer;

        int x;
    };

    Inner in;

  public:
    int get_x() {
        return in.x; // Error: int Outer::Inner::x is private.
        // Uncomment "friend" line above to fix.
    }
};

ネストされたクラスのフレンドは、自動的にそのクラスのフレンドとはみなされません。彼らが囲むクラスの友人である必要がある場合、これは別々に宣言する必要があります。逆に、囲むクラスはネストされたクラスのフレンドと自動的にはみなされないので、囲むクラスのフレンドはネストされたクラスのフレンドとはみなされません。

class Outer {
    friend void barge_out(Outer& out, Inner& in);

    class Inner {
        friend void barge_in(Outer& out, Inner& in);

        int i;
    };

    int o;
};

void barge_in(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Good.
    int o = out.o; // Error: int Outer::o is private.
}

void barge_out(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Error: int Outer::Inner::i is private.
    int o = out.o; // Good.
}

他のすべてのクラスメンバーと同様に、ネストされたクラスは、パブリックアクセスがある場合にのみクラスの外部から名前を付けることができます。ただし、明示的に名前を付けない限り、アクセス修飾子に関係なくアクセスできます。

class Outer {
    struct Inner {
        void func() { std::cout << "I have no private taboo.\n"; }
    };

  public:
    static Inner make_Inner() { return Inner(); }
};

// ...

Outer::Inner oi; // Error: Outer::Inner is private.

auto oi = Outer::make_Inner(); // Good.
oi.func();                     // Good.
Outer::make_Inner().func();    // Good.

ネストされたクラスの型エイリアスを作成することもできます。囲みクラスに型エイリアスが含まれている場合、入れ子型と型エイリアスは異なるアクセス修飾子を持つことができます。型エイリアスが囲んでいるクラスの外側にある場合は、入れ子になったクラスまたはそのtypedef公開する必要がありtypedef

class Outer {
    class Inner_ {};

  public:
    typedef Inner_ Inner;
};

typedef Outer::Inner  ImOut; // Good.
typedef Outer::Inner_ ImBad; // Error.

// ...

Outer::Inner  oi; // Good.
Outer::Inner_ oi; // Error.
ImOut         oi; // Good.

他のクラスと同様に、ネストされたクラスは、他のクラスから派生したり、派生したりすることができます。

struct Base {};

struct Outer {
    struct Inner : Base {};
};

struct Derived : Outer::Inner {};

これは、必要に応じてプログラマがネストされたクラスを更新できるようにすることで、囲むクラスが別のクラスから派生している状況で便利です。これをtypedefと組み合わせて、囲むクラスのネストされた各クラスに一貫した名前を付けることができます:

class BaseOuter {
    struct BaseInner_ {
        virtual void do_something() {}
        virtual void do_something_else();
    } b_in;

  public:
    typedef BaseInner_ Inner;

    virtual ~BaseOuter() = default;

    virtual Inner& getInner() { return b_in; }
};

void BaseOuter::BaseInner_::do_something_else() {}

// ---

class DerivedOuter : public BaseOuter {
    // Note the use of the qualified typedef; BaseOuter::BaseInner_ is private.
    struct DerivedInner_ : BaseOuter::Inner {
        void do_something() override {}
        void do_something_else() override;
    } d_in;

  public:
    typedef DerivedInner_ Inner;

    BaseOuter::Inner& getInner() override { return d_in; }
};

void DerivedOuter::DerivedInner_::do_something_else() {}

// ...

// Calls BaseOuter::BaseInner_::do_something();
BaseOuter* b = new BaseOuter;
BaseOuter::Inner& bin = b->getInner();
bin.do_something();
b->getInner().do_something();

// Calls DerivedOuter::DerivedInner_::do_something();
BaseOuter* d = new DerivedOuter;
BaseOuter::Inner& din = d->getInner();
din.do_something();
d->getInner().do_something();

上記の場合、両方BaseOuterDerivedOuterメンバー型供給Innerとして、 BaseInner_DerivedInner_それぞれ。これにより、ネストされた型を囲むクラスのインタフェースを壊さずに派生させることができ、ネストされた型を多態的に使用することができます。