Форвардное объявление вложенных типов/классов в С++

Недавно я застрял в такой ситуации:

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;

Любые идеи?

Ответ 1

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

Ответ 2

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) { }
};

Этот метод, вероятно, будет больше проблем, чем стоит, если бы были сложные конструкторы или другие специальные функции-члены, которые не были наследованы плавно. Я мог представить, что некоторые магии шаблонов плохо реагируют.

Но в моем очень простом случае это работает.

Ответ 3

Если вы действительно хотите избежать # включения неприятного заголовочного файла в файл заголовка, вы можете сделать это:

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>()
{
    // ...
}

Но тогда:

  • вам нужно будет указать встроенный тип во время вызова (особенно если ваша функция не принимает никаких параметров встроенного типа)
  • Ваша функция не может быть виртуальной (потому что это шаблон)

Итак, да, компромиссы...

Ответ 4

Это можно сделать, если forward объявит внешний класс как пространство имен.

Пример. Мы должны использовать вложенный класс others :: A :: Nested in 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();
}

Ответ 5

Я бы не назвал это ответом, но, тем не менее, интересной находкой: Если вы повторяете объявление своей структуры в пространстве имен C, все будет нормально (по крайней мере, в gcc). Когда определение класса C найдено, он, кажется, молча перезаписывает namspace 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;
}

Ответ 6

Это было бы обходным решением (по крайней мере, для проблемы, описанной в вопросе, а не для реальной проблемы, т.е. когда не было контроля над определением 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
}