Предварительное объявление вложенных типов / классов в 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;

Любые идеи?

Calmarius
источник
6
Зачем тебе это надо? Обратите внимание, что вы можете пересылать объявление, если оно является членом того же определяемого класса: class X {class Y; Y * a; }; класс X :: Y {};
Йоханнес Шауб -
Это решение сработало для меня (пространство имен C {class D;};): stackoverflow.com/questions/22389784/…
Альберт Вирш
Я нашел ссылку
bitlixi

Ответы:

224

Вы не можете сделать это, это дыра в языке C ++. Вам нужно будет удалить хотя бы один из вложенных классов.

Адам Розенфилд
источник
6
Спасибо за ответ. В моем случае это не мои вложенные классы. Я надеялся избежать огромной зависимости файла заголовка библиотеки с помощью небольшой прямой ссылки. Интересно, исправил ли это C ++ 11?
Марш Рэй
61
Ой. Только то, что я не хотел, чтобы Google появился. В любом случае, спасибо за краткий ответ.
Learnvst
19
То же самое здесь ... кто-то знает, почему это невозможно? Кажется, есть допустимые варианты использования, и этот недостаток препятствует согласованности архитектуры в некоторых ситуациях.
Мэл Нисон
Вы можете использовать друга. И просто добавьте комментарий, который вы используете, чтобы обойти дыру в C ++.
Эрик Аронести
3
Всякий раз, когда я сталкиваюсь с такими ненужными недостатками в этом языке
эрзаца
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;в производном классе, поэтому нет проблем со сложными ctors.
Xeo
1
Хороший трюк, но он не будет работать, если указатель на IDontControl :: Nested используется в том же заголовке (где он был объявлен) и доступен из внешнего кода, который также включает в себя полное определение IDontControl. (Поскольку компилятор не будет совпадать с IDontControl_Nested и IDontControl :: Nested). Обходной путь должен выполнить статическое приведение.
Артем Писаренко
Я бы порекомендовал делать все наоборот и typedef
проводить
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файл?
Нафтали ака Нил
7
lol, файл заголовка .hpp используется в проектах C ++, чтобы отличить его от файла заголовка C, который обычно заканчивается на .h. При работе с C ++ и C в одном проекте некоторые люди предпочитают .hpp и .cpp для файлов C ++, чтобы явно указать, с какими типами файлов они имеют дело, и .h и .c для файлов C.
Bitek
2

Это может быть сделано путем предварительного объявления внешнего класса как пространства имен .

Пример: мы должны использовать вложенный класс others :: 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();
}
bitlixi
источник
1
Это может работать, но без документов. Причина, по которой он работает, заключается в том, что a::bон искажается одинаково, независимо от того, aявляется ли класс или пространство имен.
Джаскмар
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;
}
nschmidt
источник
1
Я попробовал это с Cygwin GCC, и он не компилируется, если вы пытаетесь сослаться на A.someField. C :: D в определении класса A фактически ссылается на (пустую) структуру в пространстве имен, а не на структуру в классе C (кстати, это не компилируется в MSVC)
Dolphin
Это дает ошибку: «класс C» объявлен как другой тип символа »
Calmarius
9
Похоже, ошибка GCC. Кажется, что имя пространства имен может скрывать имя класса в той же области видимости.
Йоханнес Шауб -
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;
Суворов Иван
источник