LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 09-14-2011, 05:24 PM   #1
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
Interdependent C++ classes


I have a project with a lot of interdependent C++ classes, which means that I can't #include the other class's header in a header because of conflicts. So I decided to try declaring the classes using "class ClassName;" in the header and #include'ing the header in the source file, but then I get errors like this:

Code:
$ make
[  4%] Building CXX object lib/CMakeFiles/lang.dir/lang_object.cpp.o
In file included from /home/michael/projects/lang/lib/lang_object.cpp:1:0:
/home/michael/projects/lang/lib/table.hpp:32:9: error: ‘String’ does not name a type
/home/michael/projects/lang/lib/lang_object.cpp:7:24: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:12:34: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:18:36: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:24:37: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:30:36: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:36:39: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:42:42: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:61:43: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:79:44: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:98:42: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:117:48: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
/home/michael/projects/lang/lib/lang_object.cpp:133:24: error: invalid use of incomplete type ‘struct lang::LangObject’
/home/michael/projects/lang/lib/table.hpp:11:11: error: forward declaration of ‘struct lang::LangObject’
make[2]: *** [lib/CMakeFiles/lang.dir/lang_object.cpp.o] Error 1
make[1]: *** [lib/CMakeFiles/lang.dir/all] Error 2
make: *** [all] Error 2
How to fix this?
 
Old 09-14-2011, 07:40 PM   #2
vadiml
Member
 
Registered: Oct 2003
Posts: 44

Rep: Reputation: 19
You are surely using following idiom:

class NotDefinedYet;

class FullyDefined {

NotDefinedYet f1;

void method1();

};


You should use following instead:

// file FullyDefined.h

class NotDefinedYet;

class FullyDefined {

NotDefinedYet* f1;

void method1();
};

-----------
// file FullyDefined.cpp
#include "NotDefinedYet.h"
#include "FullyDefined.h"


void FullyDefined::mehod1() {
// method body
}
 
Old 09-14-2011, 07:45 PM   #3
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443

Original Poster
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
What if I need the member not to be a pointer?

Also, use code tags when post ing code.
 
Old 09-14-2011, 08:13 PM   #4
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by MTK358 View Post
What if I need the member not to be a pointer?
The class of a non-pointer, non-reference member must be defined prior to the class it's used in so the class size and member offsets are known. I can't imagine any dependency that would prevent this, other than a circular (non-pointer) dependency. You should therefore reconsider how your headers are arranged.
Kevin Barry
 
Old 09-14-2011, 08:19 PM   #5
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443

Original Poster
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
Quote:
Originally Posted by ta0kira View Post
I can't imagine any dependency that would prevent this, other than a circular (non-pointer) dependency.
The classes I'm talking about have lots of circular dependencies.
 
Old 09-14-2011, 08:42 PM   #6
johnsfine
LQ Guru
 
Registered: Dec 2007
Distribution: Centos
Posts: 5,286

Rep: Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197
It depends on what kind of circular dependency:

1) Class A has a non static member of type B and class B has a non static member of type A.

That is simply impossible to compile. There is no correct syntax, because there is no correct result from compiling it.

2) Same but with one and/or the other a static member. I'm not sure. I never wanted that circularity.

3) The common circularity: Class A and B each have inline member functions that need to use the definition of the other.

You need each class defined (including declaration of its inline functions) before the definition of the other class's inline functions. A minimal (but not recommended) sequence would be:

class B;
class A { declaring but not defining A's inline functions };
class B { defining its inline functions };
define A's inline functions;

I prefer, when a class is involved in any such circularity, to split its hpp file into two files. Normally I have A.hpp defining A and its inline functions. Instead, I would have A_declare.hpp defining class A but only declaring its inline functions, then A.hpp includes A_declare.hpp and includes other X_declare.hpp involved in the circularity, and then defines A's inline functions. Then any .cpp file needing to use A should include A.hpp but any .hpp file needing to use A includes just A_declare.hpp. It takes some extra care and some compiler option to help with warnings when you get details wrong, but it works out well.

4) There are some other circularities I wouldn't create, so I'm not sure how/whether you can sort them out: For example, each of A and B might have a member function returning by value an object of the other class. I don't know whether that could work with the approach described in (3). If the return were by pointer or reference, then it is just one of many things included in (3).

Someone I used to work with used dummy templates to resolve uglier circularities without resequencing and/or splitting the definitions. I think most of that approach is not valid C++ and won't work in newer versions of gcc. I cleaned all of that out of the code he wrote in favor of methods that took some extra effort to set up, but resulted in more maintainable code. I'm not sure whether there are some cases in which valid dummy templates can more usefully resolve circularities (there are none left in the code I maintain). I do use templates to allow some very powerful circularity between a base class and derived class:

template <class B> class A { ... };
class B : public class A<B> { ... };

Last edited by johnsfine; 09-14-2011 at 09:02 PM.
 
Old 09-14-2011, 08:45 PM   #7
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by MTK358 View Post
The classes I'm talking about have lots of circular dependencies.
You mean like A contains B, and B contains A or has A as a base class? Those things aren't possible in C++.

Looking again, it appears your error is at the point of use in the .cpp; therefore, just include the header defining lang::LangObject in the .cpp, not in the .hpp.
Kevin Barry

PS And also fix "/home/michael/projects/lang/lib/table.hpp:32:9: error: ‘String’ does not name a type". That could keep one of your classes from being defined.

Last edited by ta0kira; 09-14-2011 at 08:46 PM.
 
Old 09-15-2011, 01:55 PM   #8
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443

Original Poster
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
Quote:
Originally Posted by ta0kira View Post
Looking again, it appears your error is at the point of use in the .cpp; therefore, just include the header defining lang::LangObject in the .cpp, not in the .hpp.
I'm not sure I understand, why would lang::LangObject's header be included in itself?

Quote:
Originally Posted by ta0kira View Post
PS And also fix "/home/michael/projects/lang/lib/table.hpp:32:9: error: ‘String’ does not name a type". That could keep one of your classes from being defined.
I did that, and it didn't help.
 
Old 09-15-2011, 03:04 PM   #9
SigTerm
Member
 
Registered: Dec 2009
Distribution: Slackware 12.2
Posts: 379

Rep: Reputation: 234Reputation: 234Reputation: 234
Quote:
Originally Posted by MTK358 View Post
/home/michael/projects/lang/lib/table.hpp:32:9: error: ‘String’ does not name a type
In lang_object.cpp, make sure that header that defines "String" is included BEFORE table.hpp.

Quote:
Originally Posted by MTK358 View Post
/home/michael/projects/lang/lib/lang_object.cpp:7:24: error: invalid use of incomplete type ‘struct lang::LangObject’
Header that declares LangObject is not included in lang_object.cpp OR lang::LangObject used in non-pointer variable before the declaration of lang::LangObject structure. Include required header, or move declaration of lang::LangObject to the beginning of file.

Quote:
Originally Posted by MTK358 View Post
What if I need the member not to be a pointer?
You move every single structure into separate header, use one header per class (and one class per header) and use forward declarations + (smart) pointers when possible. See Qt 4 sources for example.

You can also use pointer to abstract base class instead of pointers to actual class to avoid some of the circular dependencies. It is possible to declare abstract base class in *.h, extend it in cpp with "private" class, and return pointer to private class declared in cpp using builder/factory method that returns pointer to base class and is visible from *.h.

It is impossible for class A to contain B and for class B to contain A as non-pointer/non-reference at the same time, since both classes would have infinite size as a result. Aside from that there are no unsolvable circular dependencies.

Quote:
Originally Posted by MTK358 View Post
I'm not sure I understand, why would lang::LangObject's header be included in itself?
IF you forgot include guard, a header can get included into itself (in a.h #include "b.h", in b.h, #include "a.h"). Situation can be solved by adding include guard or by splitting header into several different headers. Even with include guard you can still get problems if you have circular inclusions (due to declaration order).

Compiler operates on cpp files, one file at a time, doesn't know anything about other cpp files, and inserts every #included header completely into cpp file before compiling the cpp. Keep that in mind, and all problems will be easy to solve.

Last edited by SigTerm; 09-15-2011 at 03:11 PM.
 
Old 09-15-2011, 04:11 PM   #10
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by MTK358 View Post
I'm not sure I understand, why would lang::LangObject's header be included in itself?
Does this mean you're defining the class in the .cpp file with the error, but below where the errors are? I'm sure we'd make more progress if you posted some code that demonstrated the problem.
Kevin Barry
 
Old 09-15-2011, 06:13 PM   #11
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443

Original Poster
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
lang_object.hpp:
Code:
#ifndef __LANG__LANG_OBJECT_HH_INCLUDE_GUARD__
#define __LANG__LANG_OBJECT_HH_INCLUDE_GUARD__

#include "common.hpp"

namespace LANG_NAMESPACE
{
    class Function;
    class Table;
    class String;

    class LangObject : public gc_cleanup
    {
    public:
        enum Type {
            BooleanType,
            NumberType,
            StringType,
            TableType,
            FunctionType,
            NilType,
        } type;

        union {
            bool booleanValue;
            double numberValue;
            String *stringValue;
            Table *tableValue;
            Function *functionValue;
        } value;

        LangObject();
        LangObject(bool value);
        LangObject(double value);
        LangObject(String *value);
        LangObject(Table *value);
        LangObject(Function *value);
        
        static bool toBoolean(LangObject obj);
        static double toNumber(LangObject obj);
        static String* toString(LangObject obj);
        static Table* toTable(LangObject obj);
        static Function* toFunction(LangObject obj);

        inline bool isNil();
    };
}

#endif
lang_object.cpp:
Code:
#include "table.hpp"
#include "function.hpp"
#include "string.hpp"

namespace LANG_NAMESPACE {

<snip>
table.hpp:
Code:
#ifndef __LANG__TABLE_HH_INCLUDE_GUARD__
#define __LANG__TABLE_HH_INCLUDE_GUARD__

#include "common.hpp"
#include "array.hpp"
#include "assoc_array.hpp"

namespace LANG_NAMESPACE
{
    class Function;
    class LangObject;
    class String;
    
    class Table : public gc_cleanup
    {
    public:
        Table();
        ~Table();
        
        Table* addParent(Table *parent);
        bool hasParent(Table *other);
        
        bool get(const char *name, LangObject *value, Table *child = NULL);
        LangObject get(const char *name);
        void set(const char *name, LangObject value);
        void setLocal(const char *name, LangObject value);
        void del(const char *name);

        bool inherits(Table *t);
        
        bool toBoolean();
        double toNumber();
        String* toString();
        Function* toFunction();

        bool equals(LangObject b);
        bool compare(LangObject b, double *result);

        void *internalData;
        void *internalDataTypeId;
        Table *classTable;
        
    protected:
        bool set2(const char *name, LangObject value);
        
        Array<Table*> *parents;
        AssocArray<const char*, LangObject> *contents;
    };
}

#endif
table.cpp:
Code:
#include "table.hpp"
#include "function.hpp"
#include "call_param_list.hpp"
#include "bound_param_function.hpp"
#include "builtin_classes.hpp"
#include "lang_object.hpp"
#include "string.hpp"
#include <stdlib.h>
#include <string.h>

namespace LANG_NAMESPACE {

<snip>
 
Old 09-15-2011, 06:38 PM   #12
SigTerm
Member
 
Registered: Dec 2009
Distribution: Slackware 12.2
Posts: 379

Rep: Reputation: 234Reputation: 234Reputation: 234
Quote:
Originally Posted by MTK358 View Post
lang_object.cpp:
table.cpp:
lang_object.hpp should be either included from within table.hpp or it should be #included before table.hpp in all files that use table.hpp
 
Old 09-15-2011, 07:17 PM   #13
dwhitney67
Senior Member
 
Registered: Jun 2006
Location: Maryland
Distribution: Kubuntu, Fedora, RHEL
Posts: 1,541

Rep: Reputation: 335Reputation: 335Reputation: 335Reputation: 335
@ MTK358

Be wary of storing a pointer to a string, such as what seems to be your intent with this declaration:
Code:
AssocArray<const char*, LangObject> *contents;
Unless you are referencing hard-coded strings (which live for the entirety of the program), you have a good chance of losing track of the contents of your table.

Consider the following small program where in lieu of your AssocArray, I use an STL map. I prompt the user to enter their name twice, the first time for the purpose of inserting an entry into the map, the second time to retrieve what I hope will be the same entry. If you run the program, you will see that it fails to find the entry even when using the "same" string twice.
Code:
#include <map>
#include <stdexcept>
#include <string>
#include <iostream>

class Foo
{
public:
   Foo() : theMap() {}

   void set(const char* name, const int value)
   {
      theMap[name] = value;
   }

   int get(const char* name)
   {
      std::map<const char*, int>::iterator it = theMap.find(name);

      if (it != theMap.end())
      {
         return it->second;
      }
      throw std::runtime_error("Name not found.");
   }
private:
   std::map<const char*, int> theMap;
};

int main()
{
   try {
      Foo foo;

      std::string name, name2;
      std::cout << "Enter your first name: ";
      std::getline(std::cin, name);

      foo.set(name.c_str(), 10);

      while (true)
      {
         std::cout << "Enter your first name again: ";
         std::getline(std::cin, name2);
         if (name == name2) break;
      }

      int value = foo.get(name2.c_str());
   }
   catch (std::exception& e) {
      std::cerr << e.what() << std::endl;
   }
}
 
Old 09-15-2011, 07:34 PM   #14
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by MTK358 View Post
lang_object.cpp:
Code:
#include "table.hpp"
#include "function.hpp"
#include "string.hpp"

namespace LANG_NAMESPACE {

<snip>
You need to #include "lang_object.hpp" in lang_object.cpp. Also, it's strange you posted no C++ code for lang_object.cpp, given that that's where all of the compilation errors were.
Kevin Barry
 
Old 09-15-2011, 07:50 PM   #15
MTK358
LQ 5k Club
 
Registered: Sep 2009
Posts: 6,443

Original Poster
Blog Entries: 3

Rep: Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723Reputation: 723
Quote:
Originally Posted by SigTerm View Post
lang_object.hpp should be either included from within table.hpp or it should be #included before table.hpp in all files that use table.hpp
That fixed it. I didn't think of the LangObject template argument as an issue, but actually it makes sense now.

Quote:
Originally Posted by dwhitney67 View Post
Consider the following small program where in lieu of your AssocArray, I use an STL map. I prompt the user to enter their name twice, the first time for the purpose of inserting an entry into the map, the second time to retrieve what I hope will be the same entry. If you run the program, you will see that it fails to find the entry even when using the "same" string twice.
It uses strcmp instead of comparing the pointers.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Class inheritance terminology: super-classes or base classes? 2ck Programming 6 07-20-2011 10:17 AM
Classes in VC++ Diederick Programming 2 11-30-2005 10:57 AM
Possible uses for classes. RHLinuxGUY Programming 4 11-21-2005 10:10 PM
classes in C++ niteshadw Programming 2 07-05-2005 07:05 PM
OOP (PHP) classes and extended classes ldp Programming 3 03-05-2005 11:45 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 05:40 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration