Thread: circular dependencies, forward doesn't help

  1. #1
    Registered User
    Join Date
    Nov 2006
    Posts
    519

    circular dependencies, forward doesn't help

    hi,

    a little abstraction to demonstrate my problem:

    Code:
    ------------
    class1.h 
     ----------- 
     #ifndef CLASS1_H_ 
     #define CLASS1_H_ 
     #include "class2.h"  // inclusion 
     class class2;           // and forward 
     
    class class1 
     { 
     private: 
             class2 aRererence; 
     // --- gcc says: "error: field 'aRererence' has incomplete type" --- 
    
     public: 
             class1() :aReference( class2& aR )    {aReference = aR;}; 
     // --- error according to the missing type of aReference --- 
    
     
    }; 
     
    #endif /*CLASS1_H_*/ 
     
    ------------ 
     class2.h 
     ------------ 
     #ifndef CLASS2_H_ 
     #define CLASS2_H_ 
     #include <vector> 
     #include "class1.h"     // inclusion 
     class class1;              // and forward 
     class class2 
     { 
     private: 
             std::vector<class1> someContainer; 
     public: 
             class2(); 
     
    }; 
     
    #endif /*CLASS2_H_*/
    -----------------------------------------

    What is the correct way around this error? I guess I could declare the
    field as pointer, but I wanted to use the c++ reference mechanism so I can't let out the #includes, because the compiler have to know the needed memory for the initialisation.

    Yes I read the rules about using include or forward and tried the most combinations without luck.

    And for the people asking what I exactly want to do:
    class2 is a simply state of a finite state machine holding a set of transitions/conditions (=class1). Why does the conditions have to know about the state they belongs to and therefore need to include that class? because to a transition belongs a reference to the state it should switch to if it becomes true. so theres a circular dependency and i've no clue about how to redesign that without losing functionality.

    Thanks for your help in advance.

  2. #2
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    There are limits to circular relationships.

    If Object1 needs to know about Object2 size to be constructed. And Object2 needs to know about Object1 size to be constructed, then no object can be constructed.

    The key here is to pay attention to the wording: Circular Reference
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  3. #3
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    Thanks for ur comment Mario, but please don't speak in miracles i'm pretty new to c++ and that whole pointer/reference thing.

    so you mean I should declare the field as pointer and have to use the pointer instead that nice, modern c++ reference mechanism? or do I'm getting something completely wrong?

  4. #4
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    > so you mean I should declare the field as pointer and have to use the pointer instead that nice, modern c++ reference mechanism?

    Oh, no. I do mean you can use references (or pointers). What you cannot do is use the object itself.

    Code:
    class A;    // You promise you will be defining class A soon enough and
                // you are also introducing the name into the scope.
    
    class B {
        public:
            A& val1;   // it's ok. 
            A* val2;   // It's ok too. B will only be storing the address of A
            A  val3;   // Error. A needs to be fully defined. B needs to know it's size.
    };
    
    class A {
        public:
            B& val1;  // Ok. B name is known.
            B* val2;  // Ok too. For the same reasons.
            B  val3;  // Ok too. B has already been defined. B size is known.
    };
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  5. #5
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    I wish there were a more elegant solution than a forward declaration but there isn't. I've run into this problem several times on larger projects where it's impossible not to run into a situation where A needs B but B also needs A.

  6. #6
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    Thank you again. I totally missed to use the & operator in the decleration of that field, but declare as reference was exactly what I wanted to do.
    Slowly, but I'll get that reference stuff :-)

  7. #7
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    Ok I played a bit with it and tried to declare references instead of objects on some places in my code. but i cant' figure out why this couldn't work:
    ...
    Code:
    private:
    std::map<std::string, bool>& rInputMap;
    where is the difference to that:
    Code:
    A& val1;   // it's ok.
    ?

    EDIT:
    hm, I see i have to define operator= for that to work. but why?

    It is finally better to use pointers for fields? I feel that would be less complicated. but my c++ book told me to better use references if i can. what would you do?
    Last edited by pheres; 11-24-2006 at 07:50 AM.

  8. #8
    Registered User
    Join Date
    May 2006
    Posts
    903
    That would be mostly a matter of preference. The speed trade-off is insignificant unless you are coding a speed-critical application for a *very* limited machine. Even then.. I don't think it would be enough to be worth noting. I, personally, never use references. I always use pointers.

  9. #9
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    >> I played a bit with it and tried to declare references instead of objects on some places in my code.
    I don't think you can just use references and the issue will just go away. You have to pick the rigth thing to change. In the original code above, the obvious choice would be the class2 aRererence variable, but that might not be the best thing. Based on that code, you might not need to add a reference or a pointer at all. Instead of forward declaring class2 inside class1.h, you should #include "class2.h". Then inside class2.h, just forward declare class1. I don't think the vector requires the full type.

    >> I see i have to define operator= for that to work. but why?
    Why do you think you need an operator= for that? You don't, unless I'm misunderstanding you.

    >> what would you do?
    It depends on your requirements. I agree with the book, use references over pointers if you can, but it depends on the situation. You probably don't need or want either here.

  10. #10
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    > but i cant' figure out why this couldn't work:

    It will work if your class constructor assigns to rInputMap on the derivation list.

    References combine three elements of code that are sometimes hard to distinguish; Initialization versus assignment, copy-initialization versus direct-initialization and declaration versus definition.

    Let's start, shall we?

    For now keep this in mind; You can't do this:

    Code:
    int val;
    int& value;
    
    value = val;
    you have to do this:

    Code:
    int val;
    int& value = val;
    The thing with references is that they have to be initialized at the point of definition.

    Now... forget that for a moment and let's go back. There's two types of initializations; copy-initialization and direct-initialization. You can express them with objects of built-in type as such:

    Code:
    int val = 13;  // copy initialization
    int val(13);  // direct initialization
    Both define an object of type int, named val, and assign 13 to it. But they do it in different ways. The first creates a temporary object with the value 13 and then copies this object to val. The temporary is then discarded. The second directly initalizes val with 13.

    And this is where we go back to the references above. Your class constructor can have an initialization list. The real effect of this initialization list is to direct-initialize your data members. Everything else that happens inside the constructor will be copy-initialized.

    Code:
    class myClass {
        public:
            myClass(int x): val1_(x), val2_(13) { val3_ = 14; }
        private:
            int val1_;
            int val2_;
            int val3_;
    };
    What you have above as a class myClass being defined with a constructor. That constructor takes one parameter, an int, that is used to direct-initialize the data member val1_. The data member val2_ is also direct-initialized to 13, while the data member val3_ is copy-initialized to 14 inside the constructor body.

    Of course the difference can be seen as meaningless for these type of objects. However its significant for certain datatypes. Among them references and user defined classes that don't offer a default constructor. Consider:

    Code:
    class myClass {
        public:
            myClass() {
                int x;
                val1_ = x;
            }
        private:
            int& val1_;
    };
    I bet by this time you already know why the above code will fail to compile. val1_ is a reference. It has to be initialized at the time it is being defined. When you write it's name in the private section of your class you are only declaring it. You are only introducing the name. You are not actually allocating space to it in the memory. That will happen when the constructor is ran (in other words when a real instance of that class is created).

    At that time a value has to be given in order to initialize it. It wasn't. By the time the body of the constructor starts to get executed all your private data members have already been allocated in memory (they have been defined). But nowhere was your reference initialized. The compilerwill flag the error.

    Ok, let's do it right...

    Code:
    class myClass {
        public:
            myClass(int x): val1_(x) {}
        private:
            int& val1_;
    };
    And this is the anti-climatic end to a long post. References data members can only be initialized from constructor arguments, public objects or other data members. Being this last one worth a post of its own.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  11. #11
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    thank you both again. i already used the initializer list (also without the much deeper understanding your post offered to me).
    my compiler (gcc) forced me to define operator= by error message. after i inserted it even with empty body it compiled flawlessly. that behavior is even more strange knowing that direct initialization doesn't use operator=.

    here is the part of my code i figured out causing this:

    Code:
    // .h
    ...
    private:
    	/// Holds all conjunctions combined by logical OR.
    	std::vector< Conjunction> disjunction;
    ...
    void addConjunction( Conjunction&  con);
    ...
    
    // .cpp
    ...
    void Vcondition::addConjunction( Conjunction& con)
    {
    	disjunction.push_back( con );
    }
    ...
    inserting that add function created the need for an operator= defined explicitely in this class.
    for testing I switched to using pointers for fields without running into this kind of trouble.

  12. #12
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Ah. But that is a completely different object. We thought you were talking about the std::map<std::string, bool> line of code that was forcing you to create a assignment operator.

    We would have to take a look at your Conjunction class. You see, the compiler creates an assignment operator for you even if you don't code it yourself. This synthetized assignment operator does only the basic stuff (a member-wise copy operation and checks against you trying to assign an object to itself).

    However, the way you define your class may invalidate the synthetized assignment operator. Of the top of my head, one easy way you can see this happening is to include a data member with a type that doesn't allow copy operations. On that case the compiler will not create one for you and you will be leftout without a assignment operator.

    In case you need one, you will have to define it yourself.

    Now... the reason why you had to create one was probably do to that (again we would have to look at your Conjunction class)... but... It has nothing to do with the reference parameter in addConjunction().

    It has to do with the push_back() operation. Your vector is declared simply as a vector to an object of type Conjunction. Everytime you add an element to it, a new Conjunction object will be created, assigned with the argument of your function, and copied onto the new index.

    That is why. Not because it is a reference.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  13. #13
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    I see i've to dive deeply into the matter of memory allocation and initialisation to be able to predict the behavior of all cases in my own software. Thank you for you explanation Mario.

  14. #14
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    Any object you put into a vector requires a valid copy constructor and copy assignment operator. If you have a reference member, the default copy assignment operator does not get created because it cannot be created. You cannot assign to a reference (other than to assign a new value to what it refers to). So if your class has a reference member, you basically cannot store it directly in a vector.

    pheres, I still don't think you need to have a reference member, though. Did you try my suggestion of using the forward declaration in the second header instead of the first?

    Also, here are a couple corrections from Mario F.'s explanations:

    >> Everything else that happens inside the constructor will be copy-initialized.
    Everything else will be default initialized and then copy assigned.

    >> checks against you trying to assign an object to itself
    It doesn't really, although in almost all cases that won't matter.

  15. #15
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    > Everything else will be default initialized and then copy assigned.

    For non built-in objects, yes. But built-in objects will be either zero initialized if the class object is created globally, or uninitialized if local.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Does gcc hate Forward declarations?
    By SevenThunders in forum C++ Programming
    Replies: 12
    Last Post: 03-16-2009, 02:03 PM
  2. how do you resolve this error?
    By -EquinoX- in forum C Programming
    Replies: 32
    Last Post: 11-05-2008, 04:35 PM
  3. singly linked circular list
    By DarkDot in forum C++ Programming
    Replies: 0
    Last Post: 04-24-2007, 08:55 PM
  4. Circular include issue
    By einarp in forum C++ Programming
    Replies: 10
    Last Post: 07-10-2006, 08:43 PM
  5. circular shift
    By n00bcodezor in forum C Programming
    Replies: 2
    Last Post: 11-20-2001, 03:51 AM