How come this simple 10-line code can work ?! (very interesting)

This is a discussion on How come this simple 10-line code can work ?! (very interesting) within the C++ Programming forums, part of the General Programming Boards category; FileA.h Code: #include <FileB.h> #include <FileC.h> class A{ typedef B<C> Engine; std::auto_ptr<Engine> engine; ... }; ================== FileB.h Code: class A; ...

  1. #1
    Registered User
    Join Date
    Apr 2007
    Posts
    284

    How come this simple 10-line code can work ?! (very interesting)

    FileA.h
    Code:
    #include <FileB.h>
    #include <FileC.h>
    class A{
    typedef B<C> Engine;
    std::auto_ptr<Engine> engine;
    ...
    };
    ==================

    FileB.h
    Code:
    class A; //forward declaration
    template <typename handle>
    class B{
    B(A& a){...}
    };
    =================

    FileC.h
    Code:
    #include<FileA.h>
    class C{
    C(A& a) {...}
    };

    This code can pass compiling. But FileA includes FileC while FileC also includes FileA. How come that does NOT cause a problem?!

  2. #2
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,046
    Without inclusion guards, there would indeed be a problem. But with inclusion guards, it's not a problem, as long as you wrap the #include inside the guards.
    Code:
    /* one.h */
    #ifndef ONE_H
    #define ONE_H
    
    #include "two.h"
    /* ... code ... */
    
    #endif
    Code:
    /* two.h */
    #ifndef TWO_H
    #define TWO_H
    
    #include "one.h"
    /* ... code ... */
    
    #endif
    That would compile just fine.

    Are you familiar with inclusion guards? If not, I could explain them . . .

    [edit] [This assumes familiarity with the preprocessor.]
    When the compiler sees #include "two.h" inside one.h, it ends up with
    Code:
    /* one.h */
    #ifndef ONE_H
    #define ONE_H
    
    /* two.h */
    #ifndef TWO_H
    #define TWO_H
    
    #include "one.h"
    /* ... code ... */
    
    #endif
    /* ... code ... */
    
    #endif
    Right, good so far. Now it sees that #include "one.h" and gets
    Code:
    /* one.h */
    #ifndef ONE_H
    #define ONE_H
    
    /* two.h */
    #ifndef TWO_H
    #define TWO_H
    
    /* one.h */
    #ifndef ONE_H
    #define ONE_H
    
    #include "two.h"
    /* ... code ... */
    
    #endif
    /* ... code ... */
    
    #endif
    /* ... code ... */
    
    #endif
    However, that red code is discarded. Why? Because the #ifndef ONE_H macro is false. So in the end, the code in one.h and the code in two.h is included once and once only.

    The morale of the tale: Use inclusion guards, in case you perchance recursively include your header files. [/edit]
    Last edited by dwks; 11-14-2007 at 09:00 PM.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  3. #3
    Registered User
    Join Date
    Apr 2007
    Posts
    284
    Thank you, dwks. And I still have 2 questions:
    1) Is that to say, in theory we can do like this: every header file has its guard, and every header file includes everyone else. Therefore when we work on a big project, we don't need to worry about who should include who. Is that correct?
    2) Is that to say, in theory we don't need forward declaration. We can just include the header. As in my example: FileB can include FileA, so to avoid "class A;". And in this light, class B can have an object of A, rather than a reference of A. (Because forward declaration allows reference/pointer only). Is that correct?

    Thank you.

  4. #4
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,046
    Quote Originally Posted by meili100 View Post
    1) Is that to say, in theory we can do like this: every header file has its guard, and every header file includes everyone else. Therefore when we work on a big project, we don't need to worry about who should include who. Is that correct?
    Precisely. (Except for the case noted below.) Though including every header is overkill, you can certainly create a header file called "includes.h" which includes everything and include that. That's what the MSVC header stdafx.h does.

    Even if you don't do this, you don't have to worry about whether this header you're about to include already includes a header you've already included . . .

    Note that all of the standard header files like iostream and cstdio have inclusion guards, and you can include them as often as you like.
    2) Is that to say, in theory we don't need forward declaration. We can just include the header. As in my example: FileB can include FileA, so to avoid "class A;". And in this light, class B can have an object of A, rather than a reference of A. (Because forward declaration allows reference/pointer only). Is that correct?
    Not quite. Think of the example I gave. one.h includes two.h; the #include "one.h" inside two.h results in nothing happening. So one.h is satisfied, because it included two.h; however, two.h never got any code from one.h. two.h needs forward declarations of whatever was in one.h. And in case you included two.h initially instead of one.h, by the same token one.h also needs forward declarations of stuff in one.h.

    So this would make it seem as if you always need forward declarations! Well, again, not quite. In a header file (called ONE), you need a forward declaration for stuff in TWO when TWO might directly or indirectly include ONE once more.

    In other words, whenever you have mutual recursion you need forward declarations. (I need you, you need me . . .) If you design your headers correctly, you need very few mutual recursions. A door needs a car which needs a road, but a road doesn't need a car which doesn't need a door. (Okay, it does. That's a bad example, but you know what I mean.)
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  5. #5
    Registered User
    Join Date
    Apr 2007
    Posts
    284
    Thanks. For (2), if we use the approach that everyone includes the SUPER-HEADER, then we don't need to forward declare. Ehhh, right?

  6. #6
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,546
    No, the concept is the same. In the super-header, File A has declarations that depend on B, and B depends on A, it's the same thing.
    It doesn't necessarily work by putting all declarations in one header either - class B uses class A and class A uses class B, but class A can't include B.h (because it doesn't exist) and you can't move class B before class A because class B depends on class A and can't include A.h (because it doesn't exist), etc, etc.

    Usually, when you include the correct header for a class and you still see "undeclard identifier," then it's time to use forward declarations.

  7. #7
    Registered User whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    7,667
    There is also this option, which doesn't really supplant the discussion here but demonstrates why it might be useful. If some of your project's classes take a long time to compile and are quite interrelated, than you can cut down on compile time preferring forward declaration to preprocessing a bunch of headers.

    Consider this . . .
    Code:
    // FilesFwd.h
    #ifndef FILES_FWD_H__
    #define FILES_FWD_H__
    
    struct A;
    template< typename Handle > struct B;
    struct C;
    
    #endif
    
    // FileA.h
    #ifndef FILE_A_H__
    #define FILE_A_H__
    
    #include <memory>
    #include "FilesFwd.h"
    struct A  {
        typedef B< C > Engine;
        std::auto_ptr< Engine > engine;
        // . . .
    };
    
    #endif
    
    // FileB.h
    #ifndef FILE_B_H__
    #define FILE_B_H__
    
    #include "FilesFwd.h"
    template< typename Handle > 
    struct B {
        B( A &a ) {}
    };
    
    #endif
    
    // FileC.h
    #ifndef FILE_C_H__
    #define FILE_C_H__
    
    #include "FilesFwd.h"
    struct C {
        C( A &a ) {}
    };
    
    #endif
    
    // bar.cpp
    #include "FileA.h"
    #include "FileB.h"
    #include "FileC.h"
    
    int main()
    {
        A a;
        B< void > b( a );
        C c( a );
    }
    Not only are the files really included only where they matter most (bar.cpp) thus organizing things a bit, but it facilitates rapid compilation in periods of frequent change: files are compiled relatively independant of each other so that you may update A without recompiling B and C unnecessarily and vice-versa.

    The standard library uses this technique in <iosfwd>, where big classes like clog, cerr, cout and cin are predeclared.
    Last edited by whiteflags; 11-14-2007 at 11:47 PM.

  8. #8
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,497
    You want to avoid the "include everything just to be safe" approach for another reason as well. If you ever want to re-use a class say in another program, it will be a lot easier to extract if it has a minimal dependency.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. reading a file
    By nhubred in forum C++ Programming
    Replies: 3
    Last Post: 05-21-2009, 11:34 AM
  2. Getting a Very Simple Quake 2 Bot to Work
    By bengreenwood in forum C++ Programming
    Replies: 2
    Last Post: 03-19-2009, 05:36 AM
  3. Simple C++ code doesn't work
    By alex_dude_122 in forum C++ Programming
    Replies: 6
    Last Post: 10-18-2006, 12:53 PM
  4. Problem in simple code.
    By richdb in forum C Programming
    Replies: 6
    Last Post: 03-20-2006, 01:45 AM
  5. Scheduling Algo
    By BigDaddyDrew in forum C++ Programming
    Replies: 41
    Last Post: 03-08-2003, 10:00 AM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21