tags:

views:

143

answers:

7

I've been struggling with understanding how C++ classes include other classes. I'm guessing this is easier to understand without any preconceived notions.

Assume my two classes are Library and Book. I have a .h and .cpp file for each. My "main.cpp" runs a simple console app to use them. Here is a simple example:

//Library.h

#ifndef LIBRARY_H_
#define LIBRARY_H_
#endif

class Library
{

public:
 Library();
 ~ Library();

private:
 Book *database;
};

This throws an error about how "Book does not name a type". In Java I would import some package like org.me.Domain.Book. Can someone please explain how this works in C++?

+1  A: 

#include "Book.h" might work

jkeesh
That code, when placed in Library.h, will yield an error "redefinition of 'class Book'" in book.h.
Stephano
@Stephano: that's why book.h also should have a compile guard (i.e. something like `#ifndef BOOK_H #define BOOK_H ...` and an `#endif` at the very end of the file (which library.h does not).
Pontus Gagge
+7  A: 

In C++ source files are conceptually completely separate from class definitions.

#include and header files work at a basic text level. #include "myfile" simply includes the contents of the file myfile at the point at which the include directive is placed.

Only after this process has happened is the resulting block of text interpreted as C++ code. There is no language requirement that a class called Book has to be defined in a file called Book.h. Although it is highly recommended that you do follow such a convention, it's essential to remember that it's not a given when debugging missing declaration or definition issues.

When parsing your Library.h file the compiler must have seen a declaration for the identifier Book at the point at which it is used in the defintion of the class Library.

As you are only declaring a member variable of type "pointer to Book", you only need a declaration and not a full definition to be available so if Book is a class then the simplest 'fix' is to add a forward declaration for it before the definition of Library.

e.g.

class Book;

class Library
{
    // definition as before
};

Include Guards

It looks like you may have some include guard errors. Because you can only define classes once per translation units the definitions inside header files are usually protected with include guards. These ensure that if the same header is included multiple times via different include files that the definitions it provides aren't seen more than once. Include guards should be arranged something like this. Looking at your Library.h, it may be that your include guards are not terminated correctly.

myclass.h:

#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass
{
};

// The #ifndef is terminated after all defintions in this header file
#endif //MYCLASS_H
Charles Bailey
That makes sense. Thanks for clearing up the #include as well. Now, I had tried this and received an error "invalid use of incomplete type 'struct Book'". I added an include to Book.h in Library.cpp, and the error disappears. I have no idea why or if this is the right way to do things :) .
Stephano
(I am guessing it is not correct as I don't want to include the file Book.h in my Library.cpp file... I think)
Stephano
It sounds as though you need the full definition for the `Book` class for the actions that you are performing in the `Library.cpp`. If this is the case and the class is defined in `Book.h` then you should `#include "Book.h"` from `Library.cpp`. Why do you think that you don't want to do this?
Charles Bailey
I guess I'm running off Java instincts, so it's a little confusing. One reason is because I include both .h files in my Main.cpp. I guess that makes me feel like Library.cpp should see the book. This is helping quite a bit though, thank you.
Stephano
C++ has separate compilation. What is visible when compiling one `.cpp` has no relevance when compiling a different `.cpp` file. Only in the link step is everything brought together.
Charles Bailey
@Stephano, if you are compiling your source using a makefile then that error usually appears, as the compiler cannot link `Book` to any compiled object.
The Elite Gentleman
A: 

You want to include the header of the Book class in this class.

So:

#include "Book.h"

To avoid getting then a redefinition error you might want to put (in all your header files) the "#endif" preprocessor directive on the end of your file.

Your new library.h would be

#include Book.h

#ifndef LIBRARY_H_
#define LIBRARY_H_

class Library
{

public:
 Library();
 ~ Library();

private:
 Book *database;
};
#endif

This is because of the inclusion tree, the compiler is getting to the same header multiple times. The ifndef / define thing is for indicating that this header file is already processesd (and all the objects in it are already defined)

Peter Smit
A: 

The #endif in your Library.h file should be at the very end of the file, not after the #define LIBRARY_H_. Those three preprocessor directives (the two I mentioned and #ifndef LIBRARY_H_) ensure that the compiler only includes that file once. If the file were included multiple times, then your Library class would have two definitions, which is illegal. You mentioned that you got a "redefinition of 'class Book'" error, which makes me think that you might have misplaced the #endif in your Book.h file, as well.

Josh Townzen
A: 

This is a small example of how you might combine the headers and cpp files in order to use your Library class. As already pointed out the forward declaration of Book in Library is only useful if the class definition of Library only contains pointers of type Book. If you would do both forward declaration of book and including Book.h in Library.h you will get a compiler error "redefinition of class Book".

// File Book.h
#ifndef BOOK_H
#define BOOK_H    
class Book
{
};    
#endif

// File Library.h
#ifndef LIBRARY_H
#define LIBRARY_H

// forward declararion of Book
class Book;

class Library
{
  public:
    Library();    
    ~ Library();

    void CreateNewBook();

  private:
   Book* m_database;
};

// File Library.cpp
#include "Library.h"
#include "Book.h" // now neede in order to create instances of class Book

Library::Library() : m_database(NULL) {}
Library::~ Library()
{
  delete m_database;
}


void Library::CreateNewBook()
{
  m_database = new Book();
}

// main.cpp
#include "Library.h"

int main()
{
  Library myLib;

  myLib.CreateNewBook();
}
Holger Kretzschmar
+2  A: 

Java imports don't have much in common with C++ #include directive. Java import is just a convenience - when you write

import my.clever.package.with.a.very.long.name.MyClass;

the Java compiler knows, that each time you write MyClass you mean my.clever.package.with.a.very.long.name.MyClass. But if you omit import and write my.clever.package.with.a.very.long.name.MyClass everywhere, it will be fine. This is because Java compiler does two-run compilation - in first run it finds out what classes exist and what interface they have and on the second run it compiles the code knowing about all classes defined in project and also in all libraries added to project.

This is not the case with C++. C++ compilers do one-run compilation. There are several translation units (usually *.cpp files) - in your case I guess it is Library.cpp and Book.cpp. Each translation unit is compiled independently from any other and only at the end, in stage of linking, linker tries to combine results of each compilation.

Each translation unit is looked up from top to bottom and for every symbol used, there must be its declaration textually before using it. As usually many translation units (*.cpp files) use the same symbols (for example they refer to same classes), to provide that definitions are the same or each unit (which is required), those definitions are put in header files (usually *.h files). And instead of copying class definition in each unit, you just #include definition files. But behind the curtain #include means just 'put the whole content of file.h in place where I wrote #include "file.h"'.

So remember

  1. Everything must be declared before you use it.
  2. If you use something in several .cpp files, put its definition in .h file and include it before using it.

One more thing - I sometimes write declaration and sometimes definition - there are different things but it would make my answer much longer to explain it. Just do a research or ask another question about it - you are a newbie and you need much more to be able to write in C++. Anyway I recommend you C++ FAQ Lite

Tadeusz Kopec
+3  A: 

Can someone please explain how this works in C++?

Hi,

First, compiling in C++ is done in three steps: precompilation, compilation and linking. The precompilation is:

  1. looking for "#include" directives
  2. expanding macros
  3. processing conditional compilation

For 1., when you include a file, the code in the file is "pasted" into the compiled file from the first file matching the provided name found in the include path. The include path is specified to the compiler as input parameters (for gcc that is done with -I, so you can have gcc file.cpp -I. -I/usr/include and so on).

This "pasting of the code" can create problems, as one file can be (and usually is) included multiple times, in different files in your project. This means that after the preprocessor has done it's job, you might encounter multiple definitions for the same symbols. To avoid compiler errors due to this, you use an "include guard" construct that looks like this:

#ifndef SOME_UNIQUE_SYMBOL
#define SOME_UNIQUE_SYMBOL

// all the code in your file goes here

# endif // SOME_UNIQUE_SYMBOL

In this way, the first time the code is added (on the expansion of the #include by the precompilation process) it will be parsed (as SOME_UNIQUE_SYMBOL is undefined). The second time, the code is added but will not be parsed (as SOME_UNIQUE_SYMBOL should already have been defined the first time).

Microsoft C++ compilers define a #pragma once dirrective that you can use as the first line in a header file. This ensures that the precompiler will only include the file once (effectively replacing the #ifdef / #define / #endif combo).

Concretely in your example, your #endif should be the last line in the file.

This "pasting of the code" is also the reason why you separate declarations from definitions in C++: you place all declarations in header files (traditionally named something.h) and the definitions in source files (traditionally named something.cpp) and you only include header files.

Your header files should always be minimal. That is, they should only include declarations and enough #include directives for everything in the header file to be recognized (function and class names, constants and defines and so on).

Your example should be:

//Library.h

#ifndef LIBRARY_H_
#define LIBRARY_H_

class Book; // forward declaration, see below

class Library
{

public:
 Library();
 ~ Library();

private:
 Book *database;
};

#endif // moved as the last line of the file

In this example, the compiler will need to know what is the size of the Library class when compiling it. For this requirement, it needs to know how big each of the member variables of Library is.

In your case you only have a pointer to a book, so the size will be four bytes (or eight or something else, depending on processor architecture).

You still need to tell the compiler that "Book is a class" and you have two ways to do that: either use the forward declaration, or include the header file that defines the Book class (replace the class Book; code with #include "Book.h".

The forward declaration tells the compiler "Treat any Book token in the source as a class. You will find out the definition of the class later".

If Book will not be found on linking (i.e. compiled as a separate object file and linked together with Library) the compiler will raise a linker error.

If you use the #include you can also use a Book instance instead of a Book pointer in the header declaration (as when including it, you ensure the size of a Book class can be computed by the compiler when parsing Library class.

If you use the forward declaration, you will still have to use the #include in the source file of the Library class (in the .cpp class) where you actually use methods from the Book class.

utnapistim