4

I've created a class A and a Class B, and I'm trying to set a vector of type B in A, and a vector of type A in B:

class A Header:

#ifndef A_H_
#define A_H_

#include "B.h"
#include <vector>
using namespace std;
class A {

public:
    vector<B> bvector; // here is the error
    A();
};

#endif /* A_H_ */

class B header:

#ifndef B_H_
#define B_H_
#include "A.h"
#include <vector>

using namespace std;

class B {
vector<A> aVector; //Here is the error

public:
    B();
};

#endif /* B_H_ */

But I get the following errors:

"..\src/B.h:16:8: error: 'A' was not declared in this scope

..\src/B.h:16:9: error: template argument 1 is invalid

..\src/B.h:16:9: error: template argument 2 is invalid"

Which flips to A.h if I delete the bad line in B. What am I doing wrong?

Community
  • 1
  • 1
dbkoren
  • 1,305
  • 1
  • 11
  • 16
  • You have to have a forward declaration of one of them (but since you can do it for both, you should do it in both, and not `#include` either header at all). – Seth Carnegie Nov 04 '12 at 22:07
  • @SethCarnegie Forward declaration only works with pointers: http://stackoverflow.com/questions/37346/why-cant-a-forward-declaration-be-used-for-a-stdvector – num3ric Nov 04 '12 at 22:10
  • @num3ric it works anywhere that an incomplete type will suffice, which includes the declaration of a `vector`. – Seth Carnegie Nov 04 '12 at 22:13
  • @SethCarnegie, you're right it works anywhere an incomplete type suffices, but `std::vector` requires a complete type. It's undefined behaviour to instantiate most standard library templates with incomplete types, see [res.on.functions]/2 – Jonathan Wakely Nov 04 '12 at 22:21
  • @JonathanWakely well that sucks. – Seth Carnegie Nov 04 '12 at 22:32
  • The template might need to know the size of the object at instantiation, or might need to know if it is assignable, or has a noexcept move constructor, or any number of other things that require the type to be complete. – Jonathan Wakely Nov 04 '12 at 22:36

3 Answers3

2

ive created a class A and a Class B, im trying to set a vector of type B in A, and a vector of type A in B

You are creating a circular dependency between your classes. This is generally a bad thing, particularly in C++.

The compiler, in order to compile A, needs to know the definition of B (#include "B.h"). Unfortunately the B header contains a reference to A class (here's the circular reference). The compiler can't handle this situation, since A header is already included in the current TU (see include guards).

Despite the fact that often having circular references is a symptom of a bad design, you can eventually overcome the problem using forward declaration. For example you could modify B this way:

#ifndef B_H_
#define B_H_

#include <vector>

using namespace std;
class A; //forward declaration of class A
class B {
vector<A*> aVector; //note that you must only use pointer to A now 

public:
    B();
};

#endif /* B_H_ */

Using a forward declaration basically tells the compiler that a type A will be defined somewhere else. The compiler can rely on this fact, but it does not know anything about A (particularly it ignores the size of A and its methods). So inside B, if you have forward declared A, you can use only pointers to A classes (pointers have always the same size) and you can't call any methods of an A class from inside B.

Heisenbug
  • 38,762
  • 28
  • 132
  • 190
1

The trick here is that you need to forward declare one of the two classes. Unfortunately, forward declarations won't let you use the full type in the header - they'll only allow you to use a pointer to the type. So even that wouldn't work in this case.

You can use the PIMPL idiom (Pointer to IMPLementation) (google that) to circumvent this issue.

The PIMPL idiom would basically have you create a third type. Then your type B would hold a pointer to an implementation class that contained your vector of type A. All operations of the vector that was supposed to be held in B would be forwarded to the new implementation class which actually held the Vector of A.

For example:

//Forward declare impl class.
class BImpl;

class B {
    private:
        BImpl* impl;

    public:
        void PrintVector() { impl->PrintVector(); }
};

class A {
    private:
        std::vector<B> vec;

    public:
        void PrintVector() { /* Do printing */ }
};

class BImpl {
    private:
        std::vector<A> vec;

    public:
        void PrintVector() { /* Do Printing */ }
};

I haven't included the constructor or population methods for those classes, but you should get the general idea :)

John Humphreys
  • 37,047
  • 37
  • 155
  • 255
  • if i understand correctly, the solution is a bypass, and therefore i am doing something tht is wrong in c++? – dbkoren Nov 04 '12 at 22:42
  • So the compiler (preprocessor?) starts in A, includes B which itself has no include statement. It then processes BImpl, including A, but A has already been defined so nothing happens. Problem resolved. Correct? – num3ric Nov 04 '12 at 22:48
  • @user1778406 - You're not doing anything wrong necessarily. There are circumstances in which circular references are a fact of life. They can sometimes indicate that you should rethink your design - it may be overly complex. If you can design them out, great, otherwise, this is a good solution. This is actually originally a solution to decouple implementation from declaration to simplify dependencies between compilation units when building, but it works for this fine as well. – John Humphreys Nov 04 '12 at 23:04
  • @num3ric - Pretty much. B can use BImpl because it only uses a pointer which is forward declared. A can use B because B is defined first. BImpl can use A because A is defined before BImpl. You can split this between multiple files and it will work fine - I just did it all in one place for ease of demonstration. – John Humphreys Nov 04 '12 at 23:06
0

You have a circular dependency.

A.h includes B.h which includes A.h, but the A_H_ guard is already defined so the contents of A.h are skipped, so the contents of B.h are processed, which refers to A but it's not defined yet because you haven't processed anything in A.h except the #define and #include directives at the top of A.h

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521