views:

353

answers:

7

The follwing code is compiled in VC++6. I don't understand why I am getting the compilation error "C2079: 'b' uses undefined class 'B'" for the following code?

//Class B Source

#include "B.h"

void B::SomeFunction()
{
}

//Class B Header

#include "A.h"

struct A;

class B
{
    public:
        A a;
        void SomeFunction();
};

//struct A Header

#include "B.h"

class B;

struct A
{
    B b;
};

If I changed class B header to the following, then there will be no error. But the header declaration won't be at the top!!!

//Class B Header with weird header declaration

struct A;

class B
{
     public:
        A a;
        void SomeFunction();
};

#include "A.h"

Edit1: Changed Struct to struct

Edit2: Changed void SomeFunction::SomeFunction() to void B::SomeFunction()

+3  A: 
public:
    A a;

You are trying to create the object of A with only forward declaration. Compiler at this moment ( with only forward decl) cannot decide the size of the object A and hence, it cannot allocate memory required for A. So you cannot create objects with only forward decl.

Instead replace with:

A* a;

Pointer or reference to A without A's class definition will work fine.

aJ
A: 

Can you put them in the same header file? I know that isn't a particularly technical answer but you could avoid the whole thing together by doing that if it is feasible.

nmr
Doesn't matter if you put both class definitions in the same header file, one still has to come first and couldn't know the size of the other class.
Peter
thanks for the clarification, I'm still very new at this and hoped I wasn't offering bad advice, which I was
nmr
+3  A: 

Two issues jump out at me here.

1: You've written Struct A instead of struct A; note the lower-case "s". Your compiler might consider the equivalent, but I don't think it's standard C++.

You have defined a circular reference between A and B. Each A object must contain a B object, but each B object must contain an A object! This is a contradiction, and will never work the way you want it to. The usual C++ way to solve that problem is to use pointers or references for A::b or B::a (or both).

David Seiler
+3  A: 

In order to define a class or struct, the compiler has to know how big each member variable of the class is. A forward declaration does not do this. I've only ever seen it used for pointers and (less often) references.

Beyond that, what you're trying to do here cannot be done. You cannot have a class A that contains an object of another class B that contains an object of class A. You can, however, have class A contain a pointer to class B that contains an object of class A.

B.cpp

#include "B.h"

void B::SomeFunction()
{
}

B.h

#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__

Two points. First, be sure to use some form of idempotence to keep the headers from being included multiple times per compilation unit. Second, understand that in C++, the only difference between classes and structs is the default visibility level - classes use private visibility by default while structs use public visibility by default. The following definitions are functionally equivalent in C++.

class MyClass
{
public: // classes use private visibility by default
    int i;
    MyClass() : i(13) { }
};

struct MyStruct
{
    int i;
    MyStruct() : i(13) { }
};
Matt Davis
Double-underscores are reserved for the compiler. Use a different naming convention.
GMan
@GMan: I've always used this convention for my idempotence markings when the compiler doesn't generate them automatically. Is this specific to a compiler or is it in the C++ spec?
Matt Davis
@Matt, it is part of the C++ spec that such identifiers are reserved. I personally use "HEADER_PATH_TO_HEADER_H_INCLUDED" or "HEADER_PATH_TO_HEADER_INCLUDED" (if no ".h").
Michael Aaron Safyan
A: 

You alse include A.h from B.h and B.h from A.h. You should at least use preprocessor macros:

#ifndef __A_H__
#define __A_H__

// A.h contents

#endif

so that file won't be included more than once.

el.pescado
In the actual code, preprocessor macros are defined but it is not shown here to make the code simpler.
Lopper
Double-underscores are reserved for the compiler. Use a different naming convention.
GMan
A: 

If you create an instance of A, that will create an instance of B (member var) which will create an instance of A (member var) which will create an instance of B which will create an instance of A and so on... The compiler should not allow this since it requires infinite memory.

To solve this, either A or B must use a reference/pointer to the other class.

johnB
A: 

Forward declarations, like

struct A;

or

class A;

Introduce A as an incomplete type and it remains incomplete until end of type's definition is reached. There are things you can do with incomplete types and things you can't. You can

  1. Declare variables (or members) of type "pointer to A" and "reference to A"
  2. Declare functions which take arguments of type A or return type A

You can't

  1. Declare variables (nor members) of type A
  2. Dereference pointers to A or access any members of references to A
  3. Define subclasses of A.

In your code you try to declare struct member of incomplete type. It's illegal. Only pointers and references are allowed.

Tadeusz Kopec