C++ Succinctly: Functions and Classes

Declaration vs. Definition

Tip: This first section, “Declaration vs. Definition,” is a bit dense. Understanding these concepts before looking at a sample will help you understand the sample. In turn, looking at a sample will help you understand these concepts. I recommend you read this and then look through the samples in the next two sections. If parts of this section weren’t clear, come back to reread this section.

In C#, classes and other types are declared and defined at the same time. Even with the partial keyword, the class definition is simply allowed to spread over multiple files; it does not change the combination of declaration and definition. The only exception to this rule is when doing interop (which uses DllImportAttribute and the extern keyword to declare a function defined in an external DLL). In that case, the definition isn’t in C# but is almost certainly in some non-.NET library. (If the DLL was a .NET assembly, you could just add a reference to it and use it without any interop code being necessary.)

I write this because in C++, declaration and definition can usually be separated, and frequently are. It is common to see a class declared in a header file (which, by convention, has a .H suffix) and defined in a source file (which, by convention, has a .CPP suffix). This is true not just for classes, but also for stand-alone functions and even structures and unions when they have member functions associated with them.

Expect to see one or more #include "SomeHeader.h" lines at the top of a .CPP file. These statements tell the compiler (or, more accurately, the preprocessor) that there are declarations and possibly definitions in that file, or in files included from it, that are necessary for the compiler to make sense of parts of the C++ code that follows.

With Visual C++, when including a header that is part of your project or is not found in the build system’s include path, use the #include "HeaderFile.h" syntax. When including a system include file, such as Windows.h, use the #include <Windows.h> syntax. Lastly, when including an include file that is part of the C++ Standard Library (which we will discuss in more detail later), use the #include <vector> syntax (i.e. no .h is included). The meaning of the " " versus the < > syntax for including files is implementation-defined, though both GCC and Visual C++ use quoted syntax for local header files and bracketed syntax for system header files.

Note: The reason the .H suffix was left off from the C++ Standard Library include files was to avoid naming collisions with C++ compilers that already provided header files that used those names when the C++ Standard Library was introduced. They are normal header files, have no fear.

To understand why the difference between declaration and definition matters in C++, it’s important to have a basic understanding of the C++ build process. Here’s what generally happens:

  1. The preprocessor examines a source file, inserts the text of the files specified by the include statements (and the text of the files specified by their include statements, etc.), and also evaluates and acts on any other preprocessor directives (e.g., expanding macros) and any pragma directives.
  2. The compiler takes the output from the preprocessor and compiles that code into machine code, which it stores, along with other information needed for the linking phase, in an OBJ file.
    Steps 1 and 2 are repeated for each source file within the project.
  3. Steps 1 and 2 are repeated for each source file within the project.
  4. The linker examines the output files from the compiler and the library files that your project links. It finds all of the places where the compiler identified something as being declared but not defined within that particular source file. It then locates the appropriate address for the definition and patches that address in.
  5. Once everything has been linked successfully, the linker binds everything together and outputs the finished product (typically either an executable program or a library file).

An error during any of those phases will stop the build process, of course, and the previous description is only a rough sketch of the Visual C++ build chain. Compiler authors have some flexibility in exactly how they do things. For example, there’s no requirement that any intermediate files be produced, so in theory, the whole build process could be done in memory, though in practice, I doubt anyone would ever do that. So consider that list as just a rough outline, not an exact description.

I’ve been referring to everything as source files to keep the terminology simple. Within the C++ standard, these combinations of a source file plus all of its include files is referred to as a compilation unit. I mention that now only because I will be using the term a bit further along. Let’s consider the three build phases in turn.

The preprocessor doesn’t care about C++ declarations and definitions. Indeed, it doesn’t even care if your program is in C++. The only business it has with your source files is to take care of all lines that begin with a #, thus marking them as preprocessor directives. As long as those lines are properly formed, and it can find all of the included files, if any, the preprocessor will do its work, adding and removing text as directed. It will pass the results on to the compiler, typically without writing its result out to a file since compilation immediately follows preprocessing.

The compiler does care about declarations and definitions and very much is concerned with whether your program is valid C++ code or not. However, it doesn’t need to know what a function does when it comes across it. It just needs to know what the function signature is—such as int AddTwoNumbers(int, int);.

The same is true for classes, structures, and unions; as long as the compiler knows the declaration (or in the case of a pointer, simply that the particular token is a class, a structure, a union, or an enum), then it doesn’t need any definitions. With just the declaration, it knows if your call to AddTwoNumbers is syntactically correct and that the class Vehicle; is in fact a class, so it can create a pointer to it when it sees Vehicle* v;, which is all it cares about.

The linker does care about definitions. Specifically, it cares that there is one, and only one, definition matching each of the declarations in your project. The lone exception is inline functions, which end up being created in each compilation unit in which they are used. However, they are created in a way that avoids any issues with multiple definitions.

You can have duplicate declarations among the compilation units for your program; doing so is a common trick for improving build times, as long as only one definition matches a declaration (except for inlines). In order to ensure this one definition rule is met, C++ compilers tend to use something called name mangling.

This ensures each declaration is matched up with its proper definition, including issues such as overloaded functions and namespaces (which allow the same name to be reused if the uses are in different namespaces), and class, structure, union, and enum definitions nested within classes, structures, or unions.

This name mangling is what results in terrifying linker errors, which we will see an example of in the “Inline Member Functions” section.

The severability of declarations from definitions lets you build your C++ projects without recompiling each source file every time. It also lets you build projects that use libraries for which you do not have the source code. There are, of course, other ways to accomplish those goals (C# uses a different build process for instance). This is the way C++ does it; understanding that basic flow helps make sense of many peculiarities in C++ that you do not encounter in C#.


Functions

There are two types of functions in C++: stand-alone functions and member functions. The main difference between them is that a member function belongs to a class, structure, or union, whereas a stand-alone function does not.

Stand-alone functions are the most basic types of functions. They can be declared in namespaces, they can be overloaded, and they can be inline. Let’s look at a few.

Sample: FunctionsSample\Utility.h

Sample: FunctionsSample\Utility.cpp

Sample: FunctionsSample\FunctionsSample.cpp

The header file Utility.h declares and defines two inline functions, both called IsEven (making IsEven an overloaded function). It also declares three more functions: two called PrintIsEvenResult and one called PrintBool. The source file Utility.cpp defines these last three functions. Lastly, the source file FunctionsSample.cpp uses that code to create a simple program.

Any functions defined in a header file must be declared inline; otherwise, you’ll wind up with multiple definitions and a linker error. Also, function overloads need to be different by more than just their return type; otherwise, the compiler cannot make sure you are really getting the version of the method you wanted. C# is the same way, so this shouldn’t be anything new.

As seen in Utility.cpp, when you are defining a stand-alone function that is in a namespace, you need to put the namespace before the function name and separate it with the scope resolution operator. If you used nested namespaces, you include the whole namespace nesting chain—for example, void RootSpace::SubSpace::SubSubSpace::FunctionName(int param) { ... };.


Simple Class

The following sample includes a class broken into a header file and a source file.

Sample: SimpleClassSample\VehicleCondition.h

Sample: SimpleClassSample\Vehicle.h

Sample: SimpleClassSample\Vehicle.cpp

Sample: SimpleClassSample\SimpleClassSample.cpp

In Vehicle.h, we begin with a forward declaration of the VehicleCondition enum class. We will discuss this technique more at the end of the chapter. For now the key points are (1) that we could either use this forward declaration or include the VehicleCondition.h header file and (2) that the declaration of VehicleCondition must come before the class definition for Vehicle.

In order for the compiler to allot enough space for instances of Vehicle, it needs to know how large each data member of Vehicle is. We can let it know either by including the appropriate header file or, in certain circumstances, by using a forward declaration. If the declaration of VehicleCondition came after the definition of Vehicle, then the compiler would refuse to compile the code since the complier would not know how big VehicleCondition is or even what type of data it is.

In that case, a simple declaration suffices to tell the compiler what VehicleCondition is (an enum class) and how big it is. Enum classes default to using an int as their backing field unless otherwise specified. If we leave the backing field blank, but then say to use a short, or a long, or some other backing field type somewhere else, the compiler would generate a different error message, telling us we have multiple, conflicting declarations.

We then proceed to define the Vehicle class. The definition includes the declaration of its member functions and its member variables. For the most part, we do not define the member functions. The exceptions are the GetVehicleCondition member function and the GetBasis member function, which we will discuss in the “Inline Member Functions” section.

We define the other member functions of Vehicle in Vehicle.cpp. In this case, the member functions are the constructor, the destructor, and SetVehicleCondition. Typically, a function like SetVehicleCondition would be inline, so would simple constructors and destructors in the Vehicle class. They are defined separately here to illustrate how you define these types of member functions when they are not inline functions. We will discuss the odd-looking constructor syntax in the chapter devoted to constructors. The rest of the Vehicle class code should be clear.

Note: Although you are not required to adopt the ClassName.h or ClassName.cpp file naming convention, you will see it in use almost everywhere because it makes using and maintaining code easier.

The GetVehicleConditionString inline function in VehicleCondition.h returns a copy of the std::wstring created in that function, not the local value itself. Coming from C#, you might think this a bit odd without having a new keyword used. We will explore this when we discuss the automatic duration type in the chapter on storage duration.

The entry point function uses some of the C++ Standard Library’s I/O formatting functionality.


Member Functions

As discussed earlier, member functions are part of a class, structure, or union. Simply, I will talk about them as class members from here on.

Static member functions can call other static class member functions, regardless of protection level. Static member functions can also access static class member data either explicitly (i.e. SomeClass::SomeFloat = 20.0f;) or implicitly (i.e. SomeFloat = 20.0f;), regardless of protection level.

The explicit form is helpful if you have a parameter with the same name as a class member. Prefixing member data with an m_, such as m_SomeFloat, eliminates that problem and makes it clear when you are working with class member data versus local variables or parameters. That’s just a style choice, not a requirement.

Instance (i.e. non-static) member functions are automatically assigned a this pointer to the instance data for the instance on which they were called. Instance member functions can call other class member functions and access all class member data either explicitly—the same as static members using this->m_count++; for instance data—or implicitly—the same as static and instance data (e.g., m_data++;), regardless of protection level.


Inline Member Functions

In SampleClass\Vehicle.h, the GetVehicleCondition and GetBasis member functions are both declared and defined. This combination of declaration and definition is called an inline member function in C++. Since this is similar to writing methods in C#, it might be inviting to do so in C++ as well. With some exceptions, you shouldn’t do this.

As we discussed previously, when you build a C++ project, the compiler goes through each of your source files only once. It may make many passes at the same source files to optimize them, but it’s not going to come back again after it is finished.

In contrast, the compiler will come back to your header files every time they are included in another file, regardless of whether it’s a source file or another header file. This means the compiler can end up running through the code in the header files many, many times during a build.

At the beginning of the SampleClass\Vehicle.h header file, you see the #pragma once directive. This is a useful and important line. If you include the header file A.h in a source file and then include another header file that has A.h, the #pragma once directive would tell the preprocessor not to include the contents of A.h again. This prevents the preprocessor from bouncing back and forth between two header files that include each other indefinitely. It also prevents compiler errors. If A.h was included multiple times, the compiler would fail when it reached a type definition from the second inclusion of A.h.

Even with that directive, the compiler still needs to include and parse that header file code for each source file that includes it. The more things you put into your header file, the longer it takes to build each source file. This increases compilation time, which, as you will discover, can be quite lengthy with C++ projects when compared with C#.

When you do include a member function’s definition inline in the header file, the C++ compiler can make that code inline in any source file where that function is used. This typically results in faster program executions since, rather than needing to make a call to a function, the program can simply run the code in place.

Scope is preserved by the compiler, so you don’t need to worry about naming collisions between variables defined in the inline function and in a function where it is used. When dealing with code, such as the previous examples, where you are simply retrieving a member variable’s value, inline definitions can improve speed, especially if the code is executing within a loop.

There is an alternate way to define an inline member function. If you want to keep your class definitions nice and clean, with no member function definitions within them, but still want to have some inline member functions, you can do something like the following instead:

Sample: SimpleClassSample\Vehicle.h (alternate code commented out at the bottom of the file).

The result of leaving the inline keywordbr off of the VehicleGetVehicleCondition member function

The result of leaving the inline keyword
off of the Vehicle::GetVehicleCondition member function.

Linker errors are always horrible looking, by the way. The reason is that the linker no longer knows what your variables and functions were named in the source file. It only knows what the compiler transformed those names into in order to make all the names unique. This includes overload methods, which need a unique name at the linking stage so the linker can connect a call to an overloaded member function to the correct overload version of that function.

The errors in Figure 1 are simply telling us that we defined Inventory::Vehicle::GetVehicleCondition(void) more than once. Now, we know we only defined it once, just in the header file, but we have included the header file in both Vehicle.cpp and in Main.cpp in the SimpleClassSample project.

Since we intentionally forgot to add the inline keyword to the Vehicle::GetVehicleCondition function declaration, the compiler doesn’t make the code inline. Instead, it compiles it as a function in both Main.cpp and Vehicle.cpp.

This, of course, is something the compiler is fine with because it treats each source file as a unique compilation unit. The compiler doesn’t know any better, since, by the time the code reaches it, the code has already been inserted by the preprocessor stage. Only when the linker gets all the compiled code and tries to match everything up do we reach a phase where the build process says, “Hey, I already have another version of this function!” and then fails.

As you can see, there are two ways of making member functions inline. Both must be done within the header file since the compiler will evaluate the header-file code as many times as they are included, but it will only run through source files once. If you use the second method and forget an inline keyword, then you will have horrible linker errors. If you use the second method and remember the inline keyword, but define the functions within the source file, you will get horrible linker errors—this time saying there is no definition.

Tip: Don’t try to make everything inline. You will just end up with slow compile times that kill your productivity. Do inline things that make sense, like simple getter and setter functions for member variables. Like anything else, profile first, and then optimize if needed.


Protection Levels and Access Specifiers

Member functions and member data have three possible access specifiers:

  • public
  • protected
  • private

These access specifiers denote the level of accessibility that the member has. In SampleClass\Vehicle.h, you can see two examples of how these are used. Note that unlike in C#, you do not restate the access specifier in front of each member. Instead, you state the access specifier, followed by a colon (e.g., public:), and then every declaration and definition that comes after is given that level of accessibility until you reach another access specifier.

By default, class members are private. This means if you have no access specifier at the beginning of the class declaration, then all members that are declared will be private until an access specifier is reached. If none is reached, you’d have an entirely private class, which would be very odd.

Structure members default to public, so on occasion you’ll see a structure without any access specifiers. If you wish to use them in a structure, though, they work the same as in a class.

Lastly, you can use the same access specifier more than once; if you want to organize your class so you define the member functions first and then the member variables (or vice versa), you could easily do something like this:

Note: This code is expository only; it is not included in any of the samples.

The previous class definition doesn’t define anything particularly useful. However, it does serve as an example of the use of all three access specifiers. It also demonstrates that you can use specifiers more than once, such as public in the previous example.


Inheritance

When specifying classes that your class derives from In C++, you should also specify an access specifier. If not, you will get the default access levels: private for a class and public for a structure. Note that I said classes. C++ supports multiple-inheritance. This means a class or structure can have more than one direct base class or structure, unlike C# where a class can have only one parent.

C++ does not have a separate interface type. In general, multiple-inheritance should be avoided except as a workaround for lack of a separate interface. In other words, a class should have only zero or one real base class along with zero or more purely abstract classes (interfaces). This is just a personal style recommendation, though.

There are some good arguments for multiple-inheritance. For instance, say you have three groups of functionality. Each one consists of functions and data. Then say each group is unrelated to the other—there’s no connection between them, but they aren’t mutually exclusive. In this case, you may wish to put each functionality group into its own class. Then if you have a situation where you want to create a class needing two of these groups, or even all three, you can simply create a class that inherits from all three, and you’re done.

Or, you’re done as long as you didn’t have any naming conflicts in your public and protected members’ functions and variables. For example, what if all three of the functionality groups have a member function void PrintDiagnostics(void);? You’d be doomed, yes? Well, it turns out that no, you are not doomed (usually). You need to use some weird syntax to specify which base class’ PrintDiagnostics function you want. And even then you aren’t quite done.

C++ lets you specify whether you want a class to be a plain base class or a virtual base class. You do this by putting or not putting the keyword virtual before the class’ name in the base class specifier. We’ll look at a sample shortly that addresses all of this, but before we do, it’s important to understand that if you inherit a class at least twice, and two or more of the inheritance’s are not virtual, you will end up with multiple copies of that class’ data members

This causes a whole bunch of problems when trying to specify which of those you wish to use. Seemingly, the solution is to derive from everything virtually, but that has a run-time performance hit associated with it due to how C++ implementations tend to resolve virtual members. Better still, try to avoid having this ever happen in the first place, but as that’s not always possible, do remember virtual inheritance.
And now a sample to help make this all make sense:

Sample: InheritanceSample\InheritanceSample.cpp

Note: Many of the member functions in the previous sample are declared as const by including the const keyword after the parameter list in the declaration. This notation is part of the concept of const-correctness, which we will discuss elsewhere. The only thing that the const-member-function notation means is that the member function is not changing any member data of the class; you do not need to worry about side effects when calling it in a multi-threading scenario. The compiler enforces this notation so you can be sure a function you mark as const really is const.

The previous sample demonstrates the difference between virtual member functions and non-virtual member functions. The Id function in class A is non-virtual while the VirtId function is virtual. The result is that when creating a base class reference to NonVirtualClass and call Id, we receive the base class’ version of Id, whereas when we call VirtId, we receive NonVirtualClass’s version of VirtId.

The same is true for VirtualClass, of course. Though the sample is careful to always specify virtual and override for the overrides of VirtId (and you should be too), as long as A::VirtId is declared as being virtual, then all derived class methods with the same signature will be considered virtual overrides of VirtId.

The previous sample also demonstrates the diamond problem that multiple-inheritance can produce as well as how virtual inheritance solves it. The diamond problem moniker comes from the idea that if class Z derives from class X and class Y, which both derive from class W, a diagram of this inheritance relationship would look like a diamond. Without virtual inheritance, the inheritance relationship does not actually form a diamond; instead, it forms a two-pronged fork with each prong having its own W.

NonVirtualClass has non-virtual inheritance from B1, which has virtual inheritance from A, and from B3, which has non-virtual inheritance from A. This results in a diamond problem, with two copies of the A class’ member data becoming a part of NonVirtualClass’ member data. The DemonstrateNonVirtualInheritance function shows the problems that result from this and also shows the syntax used to resolve which A you want when you need to use one of A’s members.

VirtualClass has virtual inheritance from both B1, which has virtual inheritance from A, and from B2, which also has virtual inheritance from A. Since all the inheritance chains that go from VirtualClass to A are virtual, there is only one copy of A’s data; thus, the diamond problem is avoided. The DemonstrateVirtualInheritance function shows this.

Even with virtual inheritance, VirtualClass still has one ambiguity. B1::Conflict and B2::Conflict both have the same name and same parameters (none, in this case), so it is impossible to resolve which one you want without using the base-class-specifier syntax.

Naming is very important when dealing with multiple-inheritance if you wish to avoid ambiguity. There is, however, a way to resolve ambiguity. The two commented-out using declarations in NonVirtualClass demonstrate this resolution mechanism. If we decided we wanted to always resolve an ambiguity in a certain way, the using declaration lets us do that.

Note: The using declaration is useful for resolving ambiguity outside of a class too (within a namespace or a function, for instance). It is also useful if you wish to bring only certain types from a namespace into scope without bringing the entire namespace into scope with a using namespace directive. It is okay to use a using declaration within a header, provided it is inside a class, structure, union, or function definition, since using declarations are limited to the scope in which they exist. You should not use them outside of these since you would be bringing that type into scope within the global namespace or within whatever namespace you were in.

One thing I did not touch on in the sample is inheritance access specifiers other than public. If you wanted, you could write something like class B : protected class A { ... }. Then class A’s members would be accessible from within B’s methods, and accessible to any class derived from B, but not publicly accessible. You could also say class B : private class A { ... }. Then class A’s members would be accessible from within B’s methods, but not accessible to any classes derived from B, nor would they be publicly accessible.

I mention these in passing simply because they are rarely used. You might, nonetheless, come across them, and you may even find a use for them. If so, remember that a class that privately inherits from a base class still has full access to that base class; you are simply saying that no further-derived classes should have access to the base class’ member functions and variables.

More common, you will come across mistakes where you or someone else forgot to type public before a base class specifier, resulting in the default private inheritance. You’ll recognize this by the slew of error messages telling you that you can’t access private member functions or data of some base class, unless you are writing a library and don’t test the class. In that case, you will recognize the issue from the angry roars of your users. One more reason unit testing is a good idea.


Abstract Classes

An abstract class has at least one pure virtual member function. The following sample shows how to mimic a C# interface.

Sample: AbstractClassSample\IWriteData.h

Sample: AbstractClassSample\ConsoleWriteData.h

Sample: AbstractClassSample\ConsoleWriteData.cpp

Sample: AbstractClassSample\AbstractClassSample.cpp

The previous sample demonstrates how to implement an interface-style class in C++. The IWriteData class could be inherited by a class that writes data to a log file, to a network connection, or to any other output. By passing around a pointer or a reference to IWriteData, you could easily switch output mechanisms.

The syntax for an abstract member function, called a pure virtual function, is simply to add = 0 after the declaration, as in IWriteData class: void Write(int value) = 0;. You do not need to make a class purely abstract; you can implement member functions or include member data common to all instances of the class. If a class has even one pure virtual function, then it is considered an abstract class.

Visual C++ provides a Microsoft-specific way to define an interface. Here’s the equivalent of IWriteData using the Microsoft syntax:

Sample: AbstractClassSample\IWriteData.h

Rather than define it as a class, you define it using the __interface keyword. You cannot define a constructor, a destructor, or any member functions other than pure virtual member functions. You also cannot inherit from anything other than other interfaces. You do not need to include the public access specifier since all member functions are public.


Precompiled Header Files

A precompiled header file is a special type of header file. Like a normal header file, you can stick both include statements and code definitions in it. What it does differently is help to speed up compile times.

The precompiled header will be compiled the first time you build your program. From then on, as long as you don't make changes to the precompiled header, or to anything included directly or indirectly in the precompiled header, the compiler can reuse the existing compiled version of the precompiled header. Your compile times will speed up because a lot of code (e.g., Windows.h and the C++ Standard Library headers) will not be recompiled for each build.

If you use a precompiled header file, you need to include it as the first include statement of every source code file. You should not, however, include it in any header files. If you forget to include it, or put other include statements above it, then the compiler will generate an error. This requirement is a consequence of how precompiled headers work.

Precompiled header files are not part of the C++ standard. Their implementation depends on the compiler vendor. If you have any questions about them, you should look at the compiler vendor’s documentation and make sure to specify which compiler you are using if you ask in an online forum.


Forward Declarations

As we’ve discussed, when you include a header file, the preprocessor simply takes all the code and inserts it right into the source code file that it is currently compiling. If that header file includes other header files, then all of those come in too.

Some header files are huge. Some include many other header files. Some are huge and include many other header files. The result is that a lot of code can wind up being compiled again and again simply because you include a header file in another header file.

One way to avoid the need to include header files within other header files is to use forward declarations. Consider the following code:

We’ve included the SomeClassA.h, Flavor.h, and Toppings.h header files. SomeClassA is a class. Flavor is a scoped enum (specifically an enum class). Toppings is an un-scoped enum.

Look at our function definitions: We have a pointer to SomeClassA in GetValueFromSomeClassA. We have two references to SomeClassA in CompareTwoSomeClassAs. Then we have various uses of Flavor and Toppings.

In this case, we can eliminate all three of those include statements. Why? Because to compile this class definition, the compiler just needs to know the type of SomeClassA and the underlying data types of Flavor and Toppings. We can tell the compiler all of this with forward declarations.

The three lines after #pragma once tell the compiler everything it needs to know. It’s told that SomeClassA is a class, so it can establish its type for linkage purposes. It’s told that Flavor is an enum class, and thus it knows that it needs to reserve space for an int (the default underlying type of an enum class). Lastly, it’s told that Toppings is an enum with an underlying type of int, and thus it can reserve space for it as well.

If the definitions of those types in SomeClassA.h, Flavor.h, and Toppings.h did not match those forward declarations, then you would receive compiler errors. If you wanted a SomeClassA instance to be a member variable of SomeClassB, or if you wanted to pass one as an argument directly rather than as a pointer or a reference, then you would need to include SomeClassA. The compiler would then need to reserve space for SomeClassA and would need its full definition in order to determine its size in memory. Lastly, you still need to include those three header files in the SomeClassB.cpp source code file since you will be working with them within the SomeClassB member function definitions.

So what have we gained? Anytime you include SomeClassB.h in a source code file, that code file will not automatically contain all the code from SomeClassA.h, Flavor.h, and Toppings.h and compile with it. You might choose to include them if you need them, but you’ve eliminated their automatic inclusion and that of any header files they include.

Let’s say SomeClassA.h includes Windows.h because, in addition to giving you some value, it also works with a window in your application. You’ve suddenly reduced the lines of code (by thousands and thousands) that need to be compiled in any source code file that includes SomeClassB.h but does not include SomeClassA.h or Windows.h. If you include SomeClassB.h in several dozen files, you’re suddenly talking about tens to hundreds of thousands of lines of code.

Forward declarations can save a few milliseconds, or minutes, or hours (for large projects). They are not a magic solution to all problems of course, but they are a valuable tool that can save time when used properly.

Conclusion

That was a lot to take in. We've covered a lot of important aspects of the C++ language so make sure to revisit this article if you need to refresh your memory.

This lesson represents a chapter from C++ Succinctly, a free eBook from the team at Syncfusion.
Tags:

Comments

Related Articles