Прямое объявление класса, похоже, не работает в C ++
-
19-09-2019 - |
Вопрос
Следующий код скомпилирован на VC ++ 6.Я не понимаю, почему я получаю ошибку компиляции C2079: 'b' uses undefined class 'B'
для следующего кода.
Источник класса B
#include "B.h"
void B::SomeFunction()
{
}
Заголовок класса B
#include "A.h"
struct A;
class B
{
public:
A a;
void SomeFunction();
};
структурировать Заголовок
#include "B.h"
class B;
struct A
{
B b;
};
Если я изменил заголовок класса B на следующий, то ошибки не будет.Но объявление заголовка не будет находиться вверху!
Заголовок класса B со странным объявлением заголовка
struct A;
class B
{
public:
A a;
void SomeFunction();
};
#include "A.h"
Решение
Чтобы определить класс или структуру, компилятор должен знать, насколько велика каждая переменная-член класса.Прямое объявление этого не делает.Я когда-либо видел, чтобы он использовался только для указателей и (реже) ссылок.
Помимо этого, то, что вы пытаетесь сделать здесь, не может быть сделано.У вас не может быть класса A, который содержит объект другого класса B , который содержит объект из класса А.Ты может, однако, если класс A содержит A указатель к классу B , который содержит объект из класса А.
B.cpp
#include "B.h"
void B::SomeFunction()
{
}
Б.х
#ifndef __B_h__ // idempotence - keep header from being included multiple times
#define __B_h__
#include "A.h"
class B
{
public:
A a;
void SomeFunction();
};
#endif // __B_h__
A.h
#ifndef __A_h__ // idempotence - keep header from being included multiple times
#define __A_h__
#include "B.h"
class B; // forward declaration
struct A
{
B *b; // use a pointer here, not an object
};
#endif // __A_h__
Два момента.Во-первых, обязательно используйте некоторую форму идемпотенции, чтобы заголовки не включались несколько раз в единицу компиляции.Во-вторых, поймите, что в C ++ единственным различием между классами и структурами является уровень видимости по умолчанию - классы используют закрытую видимость по умолчанию, в то время как структуры используют общедоступную видимость по умолчанию.Следующие определения функционально эквивалентны в C ++.
class MyClass
{
public: // classes use private visibility by default
int i;
MyClass() : i(13) { }
};
struct MyStruct
{
int i;
MyStruct() : i(13) { }
};
Другие советы
Прямые объявления, такие как
struct A;
или
class A;
Представьте A как неполный тип и это остается неполным до тех пор, пока не будет достигнуто конечное определение типа.Есть вещи, которые вы можете делать с неполными типами, и вещи, которые вы не можете.Ты можешь
- Объявляйте переменные (или члены) типа "указатель на A" и "ссылка на A".
- Объявляйте функции, которые принимают аргументы типа A или возвращают тип A
Ты не можешь
- Объявляйте переменные (или члены) типа A
- Разыменовывать указатели на A или получать доступ к любым элементам ссылок на A
- Определите подклассы A.
В вашем коде вы пытаетесь объявить элемент структуры неполного типа.Это незаконно.Разрешены только указатели и ссылки.
public:
A a;
Вы пытаетесь создать объект A только с прямым объявлением.Компилятор в данный момент (только с прямым уменьшением) не может определить размер объекта A и, следовательно, он не может выделить память, необходимую для A.Таким образом, вы не можете создавать объекты, используя только forward decl .
Вместо этого замените на:
A* a;
Указатель или ссылка на A без определения класса A будут работать нормально.
Здесь у меня возникают два вопроса.
1:Вы написали Struct A
вместо того , чтобы struct A
;обратите внимание на букву "s" в нижнем регистре.Ваш компилятор мог бы рассмотреть эквивалент, но я не думаю, что это стандартный C ++.
Вы определили циклическую ссылку между A
и B
.Каждый объект A должен содержать B
возражать, но каждый B
объект должен содержать A
возражаю!Это противоречие, и оно никогда не будет работать так, как вы хотите.Обычный способ C ++ решить эту проблему - использовать указатели или ссылки для A::b
или B::a
(или и то, и другое).
Вы также можете включить A.h из B.h и B.h из A.h..Вы должны, по крайней мере, использовать макросы препроцессора:
#ifndef __A_H__
#define __A_H__
// A.h contents
#endif
таким образом, этот файл не будет включен более одного раза.
Если вы создадите экземпляр A, это создаст экземпляр B (member var), который создаст экземпляр A (member var), который создаст экземпляр B, который создаст экземпляр A и так далее...Компилятор не должен допускать этого, поскольку для этого требуется бесконечная память.
Чтобы решить эту проблему, либо A, либо B должны использовать ссылку / указатель на другой класс.