C ++でのネストされた型/クラスの前方宣言


197

最近、次のような状況で行き詰まりました。

class A
{
public:
    typedef struct/class {...} B;
...
    C::D *someField;
}

class C
{
public:
    typedef struct/class {...} D;
...
    A::B *someField;
}

通常、クラス名を宣言できます。

class A;

ただし、ネストされた型を転送宣言することはできません。次の場合はコンパイルエラーが発生します。

class C::D;

何か案は?


6
なぜそれが必要なのですか?定義されている同じクラスのメンバーである場合は、前方宣言できることに注意してください。Y A; }; クラスX :: Y {};
Johannes Schaub-litb 09年

この溶液は、(名前空間C {クラスD;})私のために働いstackoverflow.com/questions/22389784/...
アルバートWiersch

解決策のリンク
bitlixi

回答:


224

あなたはそれを行うことができません、それはC ++言語の穴です。ネストされたクラスの少なくとも1つをネスト解除する必要があります。


6
答えてくれてありがとう。私の場合、それらはネストされたクラスではありません。少し前方参照するだけで、巨大なライブラリヘッダーファイルの依存関係を回避したいと思っていました。C ++ 11で修正されたのでしょうか?
Marsh Ray

61
ああ。グーグルに現れてほしくないもの。とにかく簡潔な答えをありがとう。
learnvst 2012年

19
ここでも同じです... なぜそれが不可能なのか誰かが知っていますか?有効なユースケースがあるようで、この欠如はいくつかの状況でアーキテクチャの一貫性を妨げます。
MAEL二尊院

友達が使えます。そして、C ++の穴を回避するためにそれを使用しているというコメントを入れてください。
Erik Aronesty

3
このeratz言語でこのような不必要な欠陥に遭遇するたびに、私は笑うことと泣くことの間で引き裂かれます
SongWithoutWords

33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

私は次のような前方参照が必要でした:

class IDontControl::Nested; // But this doesn't work.

私の回避策は:

class IDontControl_Nested; // Forward reference to distinct name.

後で完全な定義を使用できるようになったとき:

#include <idontcontrol.h>

// I defined the forward ref like this:
class IDontControl_Nested : public IDontControl::Nested
{
    // Needed to make a forwarding constructor here
    IDontControl_Nested(int i) : Nested(i) { }
};

この手法は、スムーズに継承されなかった複雑なコンストラクターや他の特別なメンバー関数があった場合、おそらくそれよりも厄介です。特定のテンプレートマジックの反応が悪いと想像できます。

しかし、私の非常に単純なケースでは、うまくいくようです。


16
C ++ 11ではusing basename::basename;、派生クラスでコンストラクタを継承できるため、複雑なアクターには問題ありません。
Xeo

1
素敵なトリックですが、IDontControl :: Nestedへのポインターが同じヘッダー内で使用され(前方宣言されている場合)、IDontControlの完全な定義を含む外部コードからアクセスされる場合は機能しません。(コンパイラはIDontControl_NestedおよびIDontControl :: Nestedと一致しないため)。回避策は、静的キャストを実行することです。
Artem Pisarenko、2015年

私は反対のことをしてクラスを外に出しtypedef、クラスの中で使うことをお勧めします
ridderhoff

3

ヘッダーファイルに厄介なヘッダーファイルを#includeしないようにする場合は、次のようにします。

hppファイル:

class MyClass
{
public:
    template<typename ThrowAway>
    void doesStuff();
};

cppファイル

#include "MyClass.hpp"
#include "Annoying-3rd-party.hpp"

template<> void MyClass::doesStuff<This::Is::An::Embedded::Type>()
{
    // ...
}

しかしその後:

  1. 呼び出し時に埋め込み型を指定する必要があります(特に、関数が埋め込み型のパラメーターを取らない場合)。
  2. 関数は仮想ではありません(テンプレートであるため)

だから、ええ、トレードオフ...


1
hppファイルとは一体何ですか?
Naftali別名Neal

7
笑、.hppヘッダーファイルはC ++プロジェクトで使用され、通常は.hで終わるCヘッダーファイルと区別されます。同じプロジェクトでC ++とCを使用する場合、C ++ファイルの場合は.hppと.cppを使用し、処理するファイルのタイプを明示的に指定したり、Cファイルの場合は.hと.cを使用したりします。
bitek

2

これは、外部クラスを名前空間として前方宣言することで実行できます

サンプル:ネストされたクラスother :: A :: Nestedをothers_a.hで使用する必要がありますが、これは制御できません。

others_a.h

namespace others {
struct A {
    struct Nested {
        Nested(int i) :i(i) {}
        int i{};
        void print() const { std::cout << i << std::endl; }
    };
};
}

my_class.h

#ifndef MY_CLASS_CPP
// A is actually a class
namespace others { namespace A { class Nested; } }
#endif

class MyClass {
public:
    MyClass(int i);
    ~MyClass();
    void print() const;
private:
    std::unique_ptr<others::A::Nested> _aNested;
};

my_class.cpp

#include "others_a.h"
#define MY_CLASS_CPP // Must before include my_class.h
#include "my_class.h"

MyClass::MyClass(int i) :
    _aNested(std::make_unique<others::A::Nested>(i)) {}
MyClass::~MyClass() {}
void MyClass::print() const {
    _aNested->print();
}

1
動作する可能性がありますが、文書化されていません。これが機能する理由は、クラスまたは名前空間にa::b関係なく、同じ方法でマングルされるためaです。
jaskmar

3
ClangまたはGCCでは機能しません。これは、外部クラスが名前空間とは異なるものとして宣言されたことを示しています。
ドゥギ

1

私はこれを答えとはしませんが、それでも興味深い発見があります。Cという名前空間で構造体の宣言を繰り返すと、すべてが(少なくともgccで)正常です。Cのクラス定義が見つかると、暗黙的にネームスペースCを上書きするようです。

namespace C {
    typedef struct {} D;
}

class A
{
public:
 typedef struct/class {...} B;
...
C::D *someField;
}

class C
{
public:
   typedef struct/class {...} D;
...
   A::B *someField;
}

1
私はこれをcygwin gccで試しましたが、A.someFieldを参照しようとしてもコンパイルされません。クラスAのC :: D定義は、クラスCの構造体ではなく、名前空間の(空の)構造体を実際に参照します(ところで、これはMSVCでコンパイルされません)
Dolphin

エラー「「クラスC」が別の種類のシンボルとして再宣言されました」
Calmarius

9
GCCバグのようです。名前空間名は同じスコープ内のクラス名を隠すことができると考えられているようです。
Johannes Schaub-litb 2009年

0

これは回避策になります(少なくとも、質問で説明されている問題の場合-実際の問題ではなく、つまりの定義を制御できない場合C):

class C_base {
public:
    class D { }; // definition of C::D
    // can also just be forward declared, if it needs members of A or A::B
};
class A {
public:
    class B { };
    C_base::D *someField; // need to call it C_base::D here
};
class C : public C_base { // inherits C_base::D
public:
    // Danger: Do not redeclare class D here!!
    // Depending on your compiler flags, you may not even get a warning
    // class D { };
    A::B *someField;
};

int main() {
    A a;
    C::D * test = a.someField; // here it can be called C::D
}

0

クラスCとDのソースコードを変更するアクセス権がある場合は、クラスDを個別に取り出して、クラスCにクラスの同義語を入力できます。

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.