Circular dependencies of declarations


Keywords:c++ 


Question: 

I am trying to implement example of visitor pattern, but I have trouble with circular dependecies of declarations of classes. When I do forward declaration of class Visitor, classes Russia and England doesn't know that Visitor has method visit, but when I extend forward declaration of Visitor for method accept, I need to use classes England and Russia, but they need to know who Visitor is, because they are using this type in their code. I tried many variations of ordering the code, but I failed fully. Please, help me understand, what C++ needs to get this. Thanks.

#include <cstdio>
#include <vector>

using namespace std;

class Visitor;

class Land {
  public:
    virtual void accept(const Visitor *v);
};

class England : public Land {
  public:
    void accept(const Visitor *v) {
      v->visit(this);
    }
};

class Russia : public Land {
  public:
    void accept(const Visitor *v) {
      v->visit(this);
    }
};

class Visitor {
  public:
    void visit(const England *e) const {
      printf("Hey, it's England!\n");
    }

    void visit(const Russia *r) const {
      printf("Hey, it's Russia!\n");
    }
};

class Trip {
  private:
    vector<Land> *l;
  public:
    explicit Trip(vector<Land> *_l):l(_l) {}
    void accept(Visitor *v) {
      for (unsigned i = 0; i < l->size(); i++) {
        l->at(i).accept(v);
      }
    }
};

int main() {
  England england;
  Russia russia;
  vector<Land> trip_plan;
  trip_plan.push_back(england);
  trip_plan.push_back(russia);
  trip_plan.push_back(england);
  Trip my_trip(&trip_plan);
  Visitor me;
  my_trip.accept(&me);
  return 0;
}

And there is the g++ output

c++ -ansi -Wall -Wextra -Wconversion -pedantic -Wno-unused-parameter  -o vp vp.cc
vp.cc: In member function ‘virtual void England::accept(const Visitor*)’:
vp.cc:40: error: invalid use of incomplete type ‘const struct Visitor’
vp.cc:30: error: forward declaration of ‘const struct Visitor’
vp.cc: In member function ‘virtual void Russia::accept(const Visitor*)’:
vp.cc:47: error: invalid use of incomplete type ‘const struct Visitor’
vp.cc:30: error: forward declaration of ‘const struct Visitor’

6 Answers: 

class Visitor; 

class England : public Land {
  public:
    void accept(const Visitor *v); // Only declaration
};


// Define Visitor
class Visitor {
  //...
};

// Now implementation
void England::accept(const Visitor *v) {
      v->visit(this);
}


Alexy gave one part of the answer already.

However, if you're not going to implement accept for Land then you need:

class Land {
  public:
    virtual void accept(const Visitor *v)= NULL;
};


Alexey Malistov's answer does solve your problem. It just also exposes the next problem.

The gcc compiler is complaining about the vtable (which is used for classes with virtual functions, amongst other things). The rules it uses are documented (see docs):

If the class declares any non-inline, non-pure virtual functions, the first one is chosen as the "key method" for the class, and the vtable is only emitted in the translation unit where the key method is defined.

Now, Alexey's version of your class defines the non-inline, non-pure virtual function accept. So, gcc defers instantiation of the Land vtable until it sees the definition of Land::accept. Add that, and see if it works. Or, as Nicholaz says, just make it a pure virtual.

Well, I do not want to "solve" the problem. I want to understand what is wrong and why

Get used to seperating declarations from definitions. Except for the special case of templates, C++ tends to work better this way.



When you forward declaration, C++ compiler knows that there's a user defined type of this kind but it doesn't know about it's data members and methods. In order to use the full feature of this user defined type you need to include it's header file where are located all it's methods and data members before use them otherwise you just make the forward declaration and use it's methods and data members where it's header file is included. In your case you are calling the visit() method of the forward declared Visitor class, this way you inform the compiler that there's a Visitor data type, but the compiler doesn't know yet about visit() method. In order to solve this you have to remove the forward declaration and put the Visitor definition on the top of all classes. You'll have something like this

#include <cstdio>
#include <vector>

using namespace std;
class England;
class Russia;
class Visitor {
  public:
    void visit(const England *e) const {
      printf("Hey, it's England!\n");
    }

    void visit(const Russia *r) const {
      printf("Hey, it's Russia!\n");
    }
};

class Land {
  public:
    virtual void accept(const Visitor *v);
};

class England : public Land {
  public:
    void accept(const Visitor *v) {
      v->visit(this);
    }
};

class Russia : public Land {
  public:
    void accept(const Visitor *v) {
      v->visit(this);
    }
};
...


I haven't write complex C++ program in a long time but if I remember correctly, you should out the skeleton of those classes in .h file with the same name with this .c file. Then include it into this .c file.

Hope this helps.



Give all class type declaration before its usage .. i think it would work.