1. Home
  2. Training Library
  3. Programming
  4. Programming Courses
  5. Introduction to Classes and Objects in C++

Separate Compilation

Contents

keyboard_tab

The course is part of this learning path

Start course
Overview
Difficulty
Intermediate
Duration
1h 36m
Students
16
Ratings
5/5
starstarstarstarstar
Description

Ever wondered how they maintain all the data and behaviors for the items in your inventory in a video game? Or maybe you've thought about how super-complex complex systems like banking systems, store management software, automotive digital diagnostic systems and tons of other applications were built? There's often a good chance that many of these applications use some form of Object-Oriented Programming. In this course, we look at Object-Oriented Programming, focusing on objects and classes.

Learning Objectives

  • Understand how classes are designed and constructed and how objects are created from them
  • Explore both the data members and member functions from which we build classes
  • Learn how to instantiate classes as objects
  • Learn how to use a rectangle class and a book class together and test them out

Intended Audience

  • Beginner coders, new to C++
  • Developers looking to upskill by adding C++ to their CV
  • College students and anyone studying C++

Prerequisites

To get the most out of this course, you should have a basic understanding of the fundamentals of C++.

Transcript

In the previous lecture, we created our first real class house, and learned about data members also called fields, and member functions also called methods. We explored more about the const keyword, which we've used before to make variable constants. But that we discovered could number one, be used to prevent changing of data members when placed at the end of a member function header, and two, be used to prevent any function from modifying the reference passed to it with constant references. In this lecture, we will explore an important aspect of classes when dealing with C++. This is the topic of separate compilation. We're going to continue using our houses project from the previous lecture, and just refactor it to better separate the class from our primary file, main.cpp. In C++, it is common for each class to create two separate additional files, the specification file and the implementation file. The specification file contains the class itself, which will contain the data members but usually just the member function prototypes inside. The other file, the implementation file, contains the definitions for the member functions. So, let's get this set up. And our houses project will create two additional files. We have mostly worked on autopilot throughout the course so far, and right click source files in the solution explorer to create our main.cpp C++ file. This time we will add the specification file to the header files folder, and the implementation file to the source files, to live alongside of the main.cpp file. So, let's get the files set up. I'm going to add a House.h and house.cpp. So, header we have add new item. This time I want a header file, okay? And it's going to be called House.h. Leave the .h, House.h. So, now I have a header file, House.h. On source files, I'm going to add a new item and make sure it's a C++ file, not a Header File. And this is going to be called House.cpp. All right. Now, the specification file should by convention have the same name as the class and is typically a .h file, because that stands for header. Some people even add the extension .hpp, but this is mostly just a preference. Most developers use .h extension. The implementation file should also have the same name as the class, but we usually have the .cpp extension. Now, let's move our class to the .h file first and then break the implementation out into the cpp file. Then, what we're going to do is, we're going to include the .h file in both the implementation file and main.cpp where we're using it. Carefully, note that when we include local class header files, like the ones we've created, we're going to use double quotes and not the angle braces that we're used to such as the ones in main here. I will fill in the code and explain the syntax a little as I go along, and some more afterwards. So, there's some new syntax, especially with regard to the implementation file. All right. So, first of all, we're going to take our code from here, I'm going to copy house., the house rather. Actually I'm going to cut it. Let's cut house and put it in House.h. You can delete that pragma once that was at the top, and I'm going to actually highlight the public and the private here. And hit 'Tab' just so it tabs everything over. We'll work on all this other stuff in just a second. So, that's the House.h. Back in main, I'm going to put at the bottom #include "House.h", and then in House.cpp I also want to put #include "House.h", again noting the double quotes, not angle braces. So, the next thing that I need to do is, I need to move all of these right here, this time I'm going to copy, okay? So, I guess that's another thing we need here. We need string then we have to put using namespace standard, because this is actually used as one of the data types for some of the return values and also as a private data member. All right, so we do need string in here as well. I'm going to copy all of these methods and not touching the data right now. I'm going to go over into House.cpp. All right. So, here's all the implementations. We'll fix this in just a minute, but let's go back to House.h and make it appropriate. So, here's what we're going to do. I'm going to get rid of the bodies from House.h, which again, I copied it in the House.cpp first. We're going to get rid of these right now, just the bodies, okay? So, we're going to get rid of the bodies and leave just the head alone. So, there are the setters, and there's the getters and that one right here, okay? So far so good, looks pretty good. They're not doing anything, and if you hover over a function definition not found, that's because we haven't written or haven't included the function definition properly yet. If you go over to House.cpp, we have all these definitions, but you'll notice a couple of problems. So, we need to actually tell these functions, "Hey, you're not a global function, you actually belong to the class specified in House.h." And the way we're going to do that is using this syntax right here, the name of the class and two colons, okay? Which we'll talk about in just a minute. But as soon as we do that, you notice the error goes away. The name of the class and the two colons goes between the return type, and the identifier for the method, the member function, right? I'm going to copy that, and we're going to do that to each of these. So, before setNumWindow, I paste that, House ::, house :: on setColor, you notice the error goes away. Same thing for getNumStories, getNumWindows and getColor. All right. All of our errors seem to have gone away. Good, good. Now, back in main.cpp. Now, what we're going to do, is actually observe and make sure there are no errors, and if we look across here, we have included our House.h, and we don't have any other changes, this was just a matter of refactoring and separating our code out into separate classes. So, looks like we didn't break anything, so I think we're good. We just moved everything into different files, so here's the specification file, .h says what it does, but not how it does it. And then the House.cpp fills in that information. So, going back to main, and I go to debug, start without debugging, and if we don't have any errors, we should get the same output that we had before. House is read, two stories with six windows, and the house is blue with three stories and 10 windows from our printHouseData function that we wrote last time. Awesome. Looks good to me. Now, let me explain some of this a little bit. So, first, let's start with the header and reiterate some stuff and explain and expand on that. So, this is the specification file again, the header file or specification files, contains essentially just the data members and the prototypes for the member functions. We include string library in here because one of our data members is a string, and we have member functions that return a string or take a string parameters. So, this file needs to know about strings, other than that, it's pretty straightforward. Just headers, right? Just prototypes and just the data. Next, let's talk about the implementation file. This one has a little bit more syntax in it, that's new. First, we include House.h specification file at the top, so that the implementation file knows about the specification and data that lives in the class. Then, we have the implementations of all the member functions. The elephant in the room, that is to say, the big issue that is noteworthy in this file, is that we don't do anything else and just put plain function definitions and the system would have had no way of knowing the functions belong to a class. Because C++ supports global functions which we saw in previous section, that is, functions that don't belong to a class. It thinks that the functions don't by default belong to a class, that's the default that we saw just a minute ago. So, the question might be, how do we get the compiler and runtime to know that these are member functions in fact. After all, we're defining them outside the class body. The way we do this is by placing the name of the class like we did, and the scope resolution operator, which are the two colons. And that goes between the return type again and the member function identifier. So, let me say it carefully again, you place the name of the class, followed by the scope resolution operator between the return type and the identifier of the method. So, that tells the compiler in the system, "Hey, this belongs to the class house and isn't just a global function." Now, one other thing that's technically optional, at least for our current program, but it's considered good software engineering practice for C++ developers and can avoid serious issues down the line, is to use something called include guards. These use preprocessor directives just like the hash include statements at the top of our programs. So, they're read by a preprocessor which goes through the files before the compilation occurs, which is exactly what it does for the includes. The preprocessor directives, we will add to the header file right over here, are called ifndef, define and ndef. So, let's add those and then we'll discuss them. Around that, we're going to put ifndef, if the letter N and DEF, form one word and we're going to call this HOUSE_H and then put define HOUSE_H and then after the class, we have to put "endif" right there, okay? So, going over this a little bit more, so we know what's going on. If ndef stands for if not defined, we follow by a constant name that we make up. There's nothing magical about this, just because we're in House.h, but by convention we use HOUSE_H. So, the name of the class _H. You could call it sausage or horse or cow, or donkey. It doesn't really matter, as long as you're consistent with the defined statement. So, on the very next line, we define the constant name HOUSE_H, and the next time the preprocessor sees the HOUSE_H after ifndef it will be false, because it will be defined. Because remember, ifndef stands for is,  if it's not defined. So, the second time this thing sees House, it will go through and it will say, okay, it's already defined, so I don't need to go in there. So, the very first time it saw this, it says ifndef House_H it looks through the symbol table and says, I don't have anything called HOUSE_H. So, it goes inside this as if it were kind of like an if statement, because it behaves similarly. It's conditional, but it's just,  the preprocessor is doing something conditionally rather than the compiler. This is not technically quote C++ code, it's a signal to the preprocessor. So, this right here, test this and the first time it sees it, this will say, hey, that's not defined. So, it immediately defines it. So, that means the next time it will see it and say, it is defined and then we put our class specification in here in the ndef. So, what's the point of this? If you have a bunch of classes in your project and maybe a class, let's say we make a class called city block and that contains house class header and maybe another called rural village, contains the house header as well. And maybe again, main.cpp contains city block, rural village, and also the house header file as well. So, then we might experience an issue. The header file will have been included multiple times, because if you included say, city block in main, city block already includes the House.h and then we'll be doing it again by saying House.h. So, there could be conflicts and all kinds of stuff that could happen that aren't right. So, using the include guards ensures that this doesn't happen, and that the header file is only truly included once. You might have noticed that when we originally created the header file, there was a line automatically added by Visual Studio. It was pound sign pragma once. So, the pound sign is like the hashtag, right? So, we're in the Twitter generation, so that should be familiar to you. This is a non-standard preprocessor directive used by the C++ compiler that's used by Visual Studio and many other IDEs. But since it's non-standard, meaning it's not part of the C++ standard, it's not guaranteed that it will work on all platforms. The intention of hash pragma once is to do the exact same thing that the include guards do essentially. The include guards I've shown are standard syntax though and platform independent so they should work on all compilers and in all environments. Great. Before we move on, it's that time again, I have a challenge for you. I'd like you to add a member function to the House class. This means you'll have to add it to the class declaration in the prototype list in the specification file, and also to the implementation file and then test it out in our main file to make sure it works. And since it's a member function, you have to call it on the object. So, we're going to be creating a member function called print for the House class. You want to make it print out the same stuff that our global function printHouseData does, but this time we will be able to call it directly on our House objects. So, after you've written the print member function, I want you to refactor our main function to use print as a member function instead of our global printHouseData function. Big hint. You can either use the syntax we used with this keyword and arrow operator, so this arrow, or since we're inside of the class, you could access the private data directly, which is the approach I will use. So, pause the video and give it a shot. Come back when you're done or if you need some help. How did that work out? Let's do it together. So, I need to add to House.h, another method here, print const because it shouldn't change anything so we give it only as much access as it needs and no more. So, what should we do about the implementation? So over here, House.cpp, I'm going to keep it consistent but it doesn't really matter the order. Print could be at the top, it doesn't matter. But we'll do this const, and then, oops, right here. And then, what we're going to do is we're going to go back to main, I'm going to copy this code right here and move it in here, but we have a couple of changes here, right? So, instead of... we don't need an argument passed in, so no parameters required here, because we have direct access to our data. Now, to get the color, we could do this arrow color, but we are inside the class now, so we have direct access to color. There's another thing you'll notice is C out is not showing up. So, that means that we need to include iostream also, namespace standard. There we go. So, C out is back. We have color, we have the number of stories, right? And then also the number of windows. Good. Now finally, I'm going to go back to main right here and instead of calling our little global function here, I'm actually going to Control K, Control C, that will comment those out. And I'm going to call the member function, myHouse.print, yourHouse.print. And we should get again, the exact same output. Debug. Start without debugging. And we do indeed. The house is red and has 2 stories and 6 windows, the house is blue and has 3 stories, and 10 windows. All right. So, we get the same output. So, I'm happy. So, our refactoring does not always completely change the behavior. It could be the same behavior, but we've made it better by moving something that we feel maybe should be inside the class into the class. So, good work everyone. In the next lecture, we'll discuss constructors and destructors. Let's get to it.

 

About the Author
Students
297
Courses
20
Learning Paths
4

John has a Ph.D. in Computer Science and is a professional software engineer and consultant, as well as a computer science university professor and department chair.

Covered Topics