Click here to Skip to main content
Click here to Skip to main content

Object Oriented Programming with C++

, 24 Nov 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
A short introduction to Object Oriented Programming with C++, showing the basics of objects and virtual functions in an approachable manner.

Important Correction

In the two versions of this article before November 24, 2014, I'd stated that virtual destructors weren't necessary in what was the HumanInteractions8/HumanInteractions9 example. That was incorrect, and I hope earlier readers are somehow made aware of my mistake so they don't make it themselves. I'm very sorry for the mis-statement.

Introduction

Throughout the years I've seen many people say teaching Object Oriented Programming (OOP) fundamentals with C++ isn't recommended because of the language's complexity. Java seems to be the standard, and while I have no gripe with the choice, I still find myself rolling my eyes every time I hear the first assertion.

C++ tutorials seem to be a dime a dozen, and other languages are more talked about these days, but I feel the urge to write a small-ish C++ OOP tutorial for a beginner, that gets to the heart of object orientation. I won't go into much of the complexities, because an introduction should not be something that focuses on items which aren't necessary. Tutorials of this nature aren't common, as far as I've seen, so let us begin.

If you are a beginner, skip to the next section now. The following paragraphs are for more advanced programmers to understand my approach.

In many ways, this is a response to articles like Akhil Mittal's, and several other OOP introductions I've read, as well as the claim against C++. As such, I've endeavored to keep jargon to an absolute minimum, and focus on things a beginner will need to know to start understanding the bigger C++ programming picture. I believe there is one programming book that takes an approach similar to the following, but I have forgotten the title. Everything else I've come across has not been beginner friendly in my opinion, and even though the following might also have a few rough spots, I've tried to make it something a new programmer can turn to for a relatively quick grasp of the necessary concepts.

Like many, my programming interest began long ago. Back then learning was often a matter of copying code from a magazine, and then running it and fixing all the typos introduced because of the tediousness of the manual typing. Surprisingly, even though the magazines were semi-professional, their code often contained bugs. Because of that, and other issues, many of those examples were initially over my head, but the iterative process of beating my noggin into a wall eventually brought insight. I believe that is the nature of true learning, and I hope the following pays tribute to that heritage without introducing the bugs that prolonged my own learning process (but also made it deeper).

Preliminaries

A programmer needs two things: a computer and a compiler. If your computer's Operating System (OS) is Windows and you are being cheap, I recommend Microsoft's Visual Studio Community Edition (VSCE). For the price of nothing Microsoft has made an absolutely fantastic tool available for our use. If you use another OS, I can't give any recommendations due to my own unfamiliarity with those platforms.

Once you have your compiler set up by using the standard installation procedure, the next step is to create a project in it. To do this in VSCE go to 'File -> New Project,' and select a 'Visual C++ -> General -> Empty Project.' Place it wherever you want on your system, although I recommend creating a forward-thinking subdirectory structure at this point. Of course, without experience you won't know what a forward-thinking approach is.

My recommendation is to place the projects for this tutorial into a subdirectory called "[User]\Programs\MyProgs\InitialLearning." For the following, one project will suffice, but you can make each step its own project if you wish, with each of them being a subdirectory off of the InitialLearning directory.

On my system the "Programs" subdirectory is only populated with subdirectories, and includes "Backups," "Libraries," "MyProgs," "OthersProgs," "Misc," and "TheoryAndExamples." The ultimate reason behind this layout is something you won't need for our work, but will need in the future. Complex programs almost always require external libraries, and this subdirectory arrangement results in an easier way to find those libs than if they are scattered all over your hard drive. Ultimately, that is fewer mouse clicks in Visual Studio.

Object Oriented Programming (OOP)

The purpose of coding is to solve problems, even if that is only to keep the user entertained for a while. OOP is a method of mirroring the problems in code as directly as possible. The best way to understand this statement is with an example, so let us model one: a human interaction.

For our fictional case we will represent two people talking to each other. All we want to happen is for one person to say "Good morning, Jennie!," and the other to respond, "Hello Joe!" So for the name of this project use "HumanInteraction." When creating a new project in Visual Studio, if you are in the 'InitialLearning' subdirectory mentioned earlier and you create a project named 'HumanInteraction', Visual Studio will create it in a subdirectory named 'HumanInteraction,' so you don't need to manually create the location.

In C++ we use the word class to represent a desired type of object. We are modeling humans, so the C++ equivalent is class Human. A routine, sometimes called 'function,' is the method classes use to accomplish tasks. In our case we want one to 'talk' to another, so 'talk' is a good descriptive name for our first function.

Objects have attributes, just like the real world. For instance, the attributes of the previous scenario are the people's names: 'Jennie' and 'Joe.'

Let us begin expressing a 'Human' object in C++, with a 'name' attribute. The following code includes things I haven't introduced yet, but you may be able to understand the fundamental concept of what is going on given the words used. They will be explained afterwards.

class Human {
   private:
      std::string nameC;

   public:
      Human(const std::string & name) : nameC(name) { }
   };

The first new thing you will notice when reading from top to bottom is the opening brace ({). Braces are the main way to delineate objects and 'scopes' to the compiler. In this case everything between the opening brace and closing brace (the one with the ";" after it) is the definition of the 'Human' class.

By 'scope' in the previous paragraph, I mean that the items declared within the braces will be destroyed at the ending brace unless you take actions to keep that from happening. There might be more on this later, if this writing doesn't become too long.

The next item in the Human declaration is the private:. This means that items in that section are only available to the class itself. The person's name has been declared in this section, and a logical first thought upon hearing this is that your name is not 'private' to you - all your friends know it. That is true, but they have had interactions that made your name known to them. Most random strangers will have no idea about your identity unless you write it on a sticker and paste it on yourself.

It is a good idea to make as many class items as possible private. This keeps other objects from modifying them recklessly. If you made the 'name' public any other object could change it at their whim - something you most definitely don't want!

That 'name' is declared as a std::string. C++ has a standard library to make your life easier, and items in that library are 'contained' in what is called the std namespace. I will not go into namespaces here, because it takes us too far from our current topic, and gets us into those 'complexities.' But I will mention that there is a method which enables you to eliminate the std:: in front of string, and reduce typing. All that is needed is to add a line before the class:

   using namespace std;

When you start dealing with header files (which I don't cover here), be aware that it is not a good idea to 'use' namespaces in them like this. You should explicitly specify everything in headers (i.e., "std::vector"), but using namespaces in .cpp files is only problematic if they are #included in other units as header files, which I, and almost everyone else I've seen, strongly discourage.

(Some people make an exception for the standard library, and do include using namespace std; in header files, but I recommend having much more experience before you do so. I don't, because I think it's a bad habit.)

I should also explain the C at the end of nameC.

Large programs often become difficult to figure out, and it is helpful to be able to tell variables that are a permanent part of the class (such as 'name') from others that are only used within single functions. My method of delineating these is to use the C at the end of the variable name in order to indicate it is part of the 'Class.' You will often come across code that uses a prepended m_ to do the same thing, where m_ stands for 'member.' A 'C' is fewer keystrokes than 'm_', and fewer strokes are better when you can get by with them, in my opinion.

Of course, this string is an object we can place the person's name into. In our case, one of the names will be "Joe," and the other will be "Jennie." Strings are just characters placed back to back in a computer's memory.

The next line is public:. As you might suspect from our private conversation, items placed in this section can be accessed by other objects.

The Human in the following line is a very important item. It is known as a 'constructor,' and is the method that must be called in order to create a Human in the computer's memory. More could be said about constructors at this point, such as they can be made private if you want to make construction possible in a very specific way that usually isn't needed. And they can be overloaded, which we will discuss later.

In our case I've placed a const std::string & name in parenthesis after Human. This is an item the caller of the constructor must supply, and it is defined here in order to force the caller to send it. The const qualifier indicates that the std::string won't be modified by the routine. That qualification often allows the compiler to optimize the code for better speed and efficiency.

In C++, the item in the parenthesis of our our constructor is called an 'argument.' It isn't really 'arguing' about anything; the word was probably chosen because it meant something like 'talking point.' It is something passed between the 'talker' and the 'talkee.'

Interestingly, I'm unaware of a better word for this concept, even though 'argument' doesn't express the intended meaning very well. The phrase "communication point," which could be abbreviated "compoint" could be better, but 'argument' is accepted, and the word you will need to know. (Merriam-Webster's sixth definition probably originated after programming languages adopted the word.)

Following the closing parenthesis there is a colon, :. Constructors needed a way of initializing class variables before running any code within the constructor body, in order to be more efficient, and everything between the ":" and the following brace ({) are C++'s way of doing that. They are called 'initialization lists.' In this one the nameC class variable is set to whatever the calling object sets the name argument to.

Then, following the initialization list, the body of the constructor is defined. In this case the constructor doesn't need to do anything, so it is empty. Thereby the { }.

Finally, the class definition is closed with the "};". You can also say that is the end of its scope, to use an earlier word.

Before filling in the talk function let me introduce a routine required by every program in one way or another: the main function. C++'s standards require every program to have one.

The main function begins initiating - and finishes terminating - a majority of the objects and logic used in the program. It is the routine that will create and destroy our Humans. You can consider it to be the 'God' function if you want.

I said main initiates and terminates a majority of the objects. The exception to this are global items, which I also won't go into here.

With all of these preliminaries out of the way, the entire program follows. To use it within Visual Studio, right click on the 'Source Files' filter in the right hand pane (unless you've modified the default screen layout) and select 'Add -> New Item.' Then select the 'C++ file (.cpp)' option. You can leave it named as 'Source,' or rename it 'main' or another name if you wish. Once you press the 'Add' button a new file appears in the editor window. Copy and paste the following into it:

//HumanInteractions1:
//In code, lines that begin like this one, with "//", are comments.
//You can put anything in them you wish, and they won't make it into the executable.
//Therefore, you can include them in the copy/paste without any problems.

//There is another way to do comments in C++: use "/* ... */" without the quotes, where
//the "..." is everything you want in the comment block. That can be multiple lines.
//The following is an example of the second type of comment:

/*The reason for this comment is to say that the following two '#include' lines tell the
  compiler that it needs to include those files ("string" and "iostream") during the steps
  it takes to create the program.*/

//The 'string' file has all the necessary items in it to use the std::string objects
//I mentioned.  If you comment out the 'iostream' include, the compiler will complain
//that the the std namespace doesn't contain a 'cout', which is used throughout
//the code to show the text on the screen.

#include <string>
#include <iostream>

class Human {
   private:
      std::string nameC;

   public:
      Human(const std::string & name) : nameC(name) { }

      std::string name() const { return nameC; }
      
      void talkTo(const Human & person) const {
         std::cout << nameC << " says: Hello, " << person.name() << "!" << std::endl;
         }
   };

int main() {
   Human joe("Joe");
   Human jennie("Jennie");
   joe.talkTo(jennie);
   jennie.talkTo(joe);
   std::cout << std::endl << "Press 'Enter' to exit";
   std::cin.ignore();
   return 0;
   }

Download HumanInteractions1.zip - 9KB

If you press the 'Local Windows Debugger' button in Visual Studio, you should see the following:

Congratulations! You have just executed your first object oriented program. (Barring copy/paste errors, or other difficulties that should be fixable by going through the directions again.)

I should mention that the const in the std::string name() const { return nameC; } line tells the compiler that the function will not change the state of the object in any way. In this case we are simply returning a copy of the nameC variable, and returning copies doesn't modify the original. Similar to the previously mentioned const, this allows the compiler to optimize for additional speed in some cases. (The same holds true for the second const after talkTo.)

I should also briefly introduce cout. It is simply the console output, which is the window in the screenshot. In the code, the << can be thought of as sending the items following it to the console. For instance, std::cout << std::endl << "Press 'Enter' to exit"; sends an endline (like a 'Return' on an old fashioned typewriter, or 'Enter' in a word processor), followed by the phrase "Press 'Enter' to exit." The << after std::endl allows the following string to be strung together with the std::endl.

If you want to reduce typing, the following is the program with the using namespace std; included:

#include <string>
#include <iostream>

using namespace std;

class Human {
   private:
      string nameC;

   public:
      Human(const string & name) : nameC(name) { }

      string name() const { return nameC; }
      
      void talkTo(const Human & person) const {
         cout << nameC << " says: Hello, " << person.name() << "!" << endl;
         }
   };

int main() {
   Human joe("Joe");
   Human jennie("Jennie");
   joe.talkTo(jennie);
   jennie.talkTo(joe);
   cout << endl << "Press 'Enter' to exit";
   cin.ignore();
   return 0;
   }

From the preceding discussion you might already have an idea of what is occurring in this program, but let me touch upon a few items.

First, as mentioned previously, everything is initiated in main. A Human named "Joe" is created, then "Jennie" is created. Joe 'talks' to Jennie, and she responds.

And note that I changed the function name to talkTo instead of talk as I said earlier. The reason is because I realized talkTo is a more descriptive, richer name than talk in this case. Better descriptions in the code itself make it easier to debug. The process of changing things this way is called 'refactoring.'

After Jennie and Joe 'talk,' the program waits for you to press 'Enter' so it can terminate.

One question you may have is why "Joe" is also referred to as joe in the code, without capitalization. The answer is that the "Joe" (in quotes) is the 'attribute' of the computer object joe. That attribute could be changed to anything, like "Mary", but the computer would still access it through the variable joe.

Another valid question is why didn't I name the object Joe instead of joe? Habit and consistency, in order to make things simple for reading are the reasons.

I, and most other programmers, like to distinguish Object declarations from variables. An easy way to do so is to use Capitals for Object declarations, and camelCase for the instantiation of those declarations in variables. You will come across other methods of approaching this problem in your future, but the method I am presenting is used in many places.

There are also many other ways of indenting code. For example, one of the most used looks like this:

#include <string>
#include <iostream>

using namespace std;

class Human
{
private:
   string nameC;

public:
   Human(const string & name) : nameC(name) { }

   string name() const { return nameC; }
   
   void talkTo(const Human & person) const
   {
      cout << nameC << " says: Hello, " << person.name() << "!" << endl;
   }
};

int main()
{
   Human joe("Joe");
   Human jennie("Jennie");
   joe.talkTo(jennie);
   jennie.talkTo(joe);
   cout << endl << "Press 'Enter' to exit";
   cin.ignore();
   return 0;
}

I have my own logical reasons for using the earlier style - namely the indention level automatically delineates everything associated with an indention 'block.' I consider the closing brace to be part of the block, which makes finding the next indention level much simpler. But holy wars have raged over this issue, so when you see other styles, accept them and continue on.

Object Relationships

The preceding program introduced the heart of programming C++ in an object oriented manner. But there is one more topic I am compelled to add, because without an understanding of it your programming knowledge will be limited for some time.

Objects are often related to other objects, just as items in real life are related to other items. Many times you need to create a list of objects that are somehow similar, but not all the same, and then take different actions based upon those differences.

As an example, let us deal with a list of people saying 'Hi' to other people. I will tread on stereotypes, and do so badly, so bear with me, and feel free to correct all the mistakes in the comments.

As you know, almost every culture has its own language, or local accent. In a non-object-oriented programming language, dealing with these differences is usually much more difficult than doing so in object orient languages. In fact, the object-oriented approach gives a huge advantage in ease of coding as well as run time performance in most cases.

Imagine you have a room full of people, and imagine they say 'Hi' to each other. Let us make this into an unlikely scenario, but something that is fairly easy to mentally picture. These people are arranged in a circle, and the first person says "Hi" to all the other people, one at a time, starting with the person on their left, who can be considered to be person number 2. After #1 says "Hi" to everyone, #2 then says "Hi" to #1, then #3, #4, etc, until he/she finishes, and #3 repeats the process, then the next person, until everyone has greeted everyone else.

Now, to throw the final twist into it, instead of "Hi," the talker will use their own cultural word. I won't go into the complete scenarios, because I don't know them (for instance, in Japanese 'David' sounds more like 'Dahvid' if memory serves correctly).

To give an idea of how object orientation eases programming, I need to show the basics of how a non-OO approach tackles the problem. In that approach the program would have to iterate through the people and determine their nationality/culture while performing the talk routine. C++ is perfectly capable of handling non-OO programs, so the loop to do so would look something like the following. (I've kept some object orientation elements in, to make it easier to understand.)

   //The following line iterates over the person doing the talking:
   for (int talkingPerson=0; talkingPerson<numberOfPeople; ++talkingPerson) {
   
      //And this next line iterates over who is being spoken to:
      for (int talkedToPerson=0; talkedToPerson<numberOfPeople; ++talkedToPerson) {
      
         if (talkingPerson != talkedToPerson) { //Nobody says 'hi' to themselves
         
            //And finally, they do the actual talking, depending on the culture of the talker:
            if (talkingPerson.culture() == "Japanese") cout << "Konnichiwa, " <<
                        talkedToPerson.name();
            else if (talkingPerson.culture() == "German") cout << "Hallo, " <<
                        talkedToPerson.name();
            else if (talkingPerson.culture() == "Hawaiian") cout << "Aloha, " <<
                        talkedToPerson.name();
            //...
            }
            
         }
      }

As I said, I used elements of object orientation for ease of comprehension. It isn't important that you understand everything the code is doing, but it is important to note that the inner portion of the code, consisting of the if statements (which I've bolded and italicized), quickly becomes very long as more cultures are involved. The computer will have to go through those checks every iteration, which takes a little time. That time is almost insignificant to us, because computers are incredibly fast today, but that time still passes, and can be reduced.

Object orientation greatly simplifies the code. The loop becomes:

   for (int talkingPerson=0; talkingPerson<numberOfPeople; ++talkingPerson) {
      for (int talkedToPerson=0; talkedToPerson<numberOfPeople; ++talkedToPerson) {
         if (talkingPerson != talkedToPerson) { //Nobody says 'hi' to themselves
            talkingPerson->talkTo(talkedToPerson);
            }
         }
      }

That's it. All of the cultural if processing is eliminated. And adding more cultures doesn't affect this portion of the code. Also, the time that is taken for each loop is pretty much the same whether one culture or a thousand are involved.

Pointers/References

Before developing a compilable 'talking' program for our new scenario, and showing the details involved, there is one more item I need to touch upon: the difference between "." and "->" in C++. These are two methods of referring to objects and variables.

Everything you create in a program resides in memory somewhere. For example, let's take our earlier joe. It was an object created in main, and we accessed its functions with the "." syntax. (I'm talking about the line, joe.talkTo(jennie);.)

As a vast understatement, let us say there are only 1,000 memory slots on the computer running that program. And the memory location where the joe object begins is at #703. When the program is compiled (remember pressing the 'Local Windows Debugger' button in Visual Studio?), the executable knows how to directly access #703 when it is given the term 'joe'. In those situations you simply use the "." syntax.

But there is one more way to access joe if you wish, and that is through what is called a pointer. It is perfectly acceptable to put the beginning location of joe into another memory position, and use that secondary position to 'point' to joe. To say this another way, we can place "703" into that other location. In fact, it is often quite useful to do this. When joe is accessed that way, we use the "->" syntax.

To simplify at the cost of not always being correct, but to give a general mental picture, if we are dealing with the real location of something, we use ".". If we are dealing with a pointer to the location, we use "->". (A hard rule that is much more correct is "If we are dealing with a pointer and don't dereference it, we always use the -> nomenclature to access the functions associated with the object." But you don't know what 'dereferencing' is, yet, so pretend you didn't read that!)

The simple rule is simple, but it is easy to get confused due to the fluidity with which pointers can become non-pointers, and non-pointers (called references in some circumstances, objects, or variables in others) can become pointers. These are transformed by the 'address of' operator, "&", and the 'dereferencing' operator, "*".

The 'address of' operator, "&", is used to determine the memory location of a variable or object. And, as you may be guessing, the 'dereferencing' operator, "*", is used to treat the value held in a pointer as the direct location of the variable or object. (Now you don't have to pretend any more.)

Unfortunately, both of these operators have secondary meanings in other contexts, to confuse the issue. (Or 'fortunately,' for the larger scope of C++, because they are valuable constructs, but they make your initial learning a little more difficult.) The 'address of' operator, "&", can also be used to delineate a variable called a reference, and the 'dereferencing' operator, "*", can be used to indicate a variable that is a pointer. With a little familiarity they are easy to tell apart, but it took me a while to become comfortable with them, so if you don't immediately get it, revisit the topic a few more times. You are not alone.

(As a general rule, if these symbols are on the left side of an '=' sign they are declaring a pointer or reference variable. If they are on the right, they are dereferencing or taking the address of something.)

Let us redo the previous program to make Jennie and Joe talk to each other repetitiously, using these constructs.

//HumanInteractions2:
#include <string>
#include <iostream>

using namespace std;

class Human {
   private:
      string nameC;

   public:
      Human(const string & name) : nameC(name) { }

      string name() const { return nameC; }
      
      void talkTo(const Human & person) const {
         cout << nameC << " says: Hello, " << person.name() << "!" << endl;
         }
   };

   
int main() {
   Human joe("Joe");
   Human jennie("Jennie");
   joe.talkTo(jennie);
   jennie.talkTo(joe);
   
   //Now for some new stuff! Let us get pointers to 'joe' and 'jennie'.
   
   //The following line creates a pointer to a Human object. The pointer is called
   //'pointerToJoe'. It is located in memory at the program's current stack position,
   //to give you a phrase for further study when you are ready for it.  You don't have
   //control over that memory position's location.
   //After declaring the pointer, the 'address of' operator is used to get the location
   //of 'joe', and place it into memory at the address of the pointer.
   Human * pointerToJoe = &joe;
   
   //Now do the same for jennie:
   Human * pointerToJennie = &jennie;
   
   //And use these pointers to 'talk' to the objects:
   pointerToJoe->talkTo(jennie);
   pointerToJennie->talkTo(joe);
   
   //And to make it even more demanding, brain-wise, let us 'dereference' the pointers
   //for the 'talkTo' arguments:
   pointerToJoe->talkTo(*pointerToJennie);
   pointerToJennie->talkTo(*pointerToJoe);
   
   //As if that syntax wasn't bad enough to remember, we can also get references from
   //those pointers (which is what we really did in the last example, where we used
   //"*pointerToJennie")
   Human & referenceToJoe = *pointerToJoe;
   Human & referenceToJennie = *pointerToJennie;
   
   //If it helps, in my head I almost always read "*pointerToJennie" and similar
   //dereferences as: "What's pointed to by the 'pointerToJennie.'"
   //It's far more verbose, but it aids my mental understanding, even after years of
   //seeing them.
   
   //And with these references we use the original "." syntax again:
   referenceToJoe.talkTo(referenceToJennie);
   referenceToJennie.talkTo(referenceToJoe);   
   
   cout << endl << "Press 'Enter' to exit";
   cin.ignore();
   return 0;
   }

Download HumanInteractions2.zip - 9KB

As you can see by the output, all of these are equivalent, it is just the syntax that differs:

Pointers and references are powerful, but demanding tools for your toolbelt. When used like the previous, they are safe, but as you get deeper into C++ there are many instances where pointers will bite you in the ankle with a venomous sting that will be remembered for some time. References also have some 'gotcha's associated with them, but they usually don't bite quite so hard. (Here's an article on pointers, and here's an article on the reference issues, for continued learning once you are done with this.)

Virtual(ly Magic!)

The reason I brought up pointers is because in C++ they can be used in a very powerful way: to call virtual functions.

In order to understand virtual functions, let me step back and talk about another item: base classes and sub-classes.

Remember the set of if statements that were going to rapidly grow as the number of cultures involved increased in our 'talking' scenario? Those statements were handling different types of Humans, and an easy way to picture base classes and sub-classes is to think of a generic Human as a base class, whereas a ChinesePerson is a sub-class of Human. (AmericanPerson is also a sub-class, so no superiority is implied for anybody.)

Here is that relationship expressed in C++:

class Human {
 
   };

class ChinesePerson : public Human {

   };

There is no reason to stop there, although I will as far as code goes. We could derive NorthernChinesePerson from ChinesePerson, and further derive more specific types of northerners from that class if we so desired.

Given the previous code definition, the ChinesePerson has access to all the public items in Human, and it can also access anything defined as protected in the Human class.

To give more insight, let me put together a quick program.

Pretty much every human can 'talk', so I will modify the original program around that to clarify things. First, let us make a program in which the base class has a talkTo function as a public method:

//HumanInteractions3:
#include <string>
#include <iostream>

using namespace std;


class Human {
   private:
      string nameC;

   public:
      Human(const string & name) : nameC(name) { }

      string name() const { return nameC; }
      
      void talkTo(const Human & person) const {
         cout << nameC << " says: Hello, " << person.name() << "!" << endl;
         }
   };


class ChinesePerson : public Human {
   public:
      ChinesePerson(const string & name) : Human(name) { }
   };


int main() {
   Human joe("Joe");
   ChinesePerson li("Li");
   
   //By deriving 'joe' from 'Human', and 'li' from 'ChinesePeople', I don't mean to 
   //say Joe is human and Chinese people are a subset of humanity. That will be fixed
   //in a coming iteration of this program. Doing it this way allows me to make a
   //point about 'private' and 'public' that I can't in the correct version.

   li.talkTo(joe);

   //And 'joe' can talk to 'li':
   joe.talkTo(li);
   
   cout << endl << "Press 'Enter' to exit";
   cin.ignore();

   return 0;
   }

Download HumanInteractions3.zip - 9KB

Upon running, you will see that this executes pretty much as before:

Both joe and li are able to talk to each other using the talkTo function. But if talkTo is moved into the private section of Human, only joe will be able to use it. Change the Human definition to the following, and try rerunning the program:

class Human {
   private:
      string nameC;
      
      void talkTo(const Human & person) const {
         cout << nameC << " says: " << "Hello, " << person.name() << "!" << endl;
         }

   public:
      Human(const string & name) : nameC(name) { }

      string name() const { return nameC; }
   };

Now you get an error saying that Human::talkTo" is inaccessible for the li.talkTo(joe); line:

That is because Humans, and only Humans can access items in private sections of their class. So let us make the talkTo function public again.

Now we start facing the interesting question we began with: how to get li to talk in his own language. Right now all he can do is say "Hello, [name]".

The first iteration to a solution is to give ChinesePeople their own talkTo function:

//HumanInteractions4:
#include <string>
#include <iostream>

using namespace std;


class Human {
   protected:
      string nameC;

   public:
      Human(const string & name) : nameC(name) { }

      string name() const { return nameC; }

      void talkTo(const Human & person) const {
         cout << nameC << " says: Hello, " << person.name() << "!" << endl;
         }

   };


class ChinesePerson : public Human {
   public:
      ChinesePerson(const string & name) : Human(name) { }

      void talkTo(const Human & person) const {
         cout << nameC << " says: Néih hóu, " << person.name() << "!" << endl;
         }
   };


int main() {
   Human joe("Joe");
   ChinesePerson li("Li");

   li.talkTo(joe);

   //And 'joe' can talk to 'li':
   joe.talkTo(li);
   
   cout << endl << "Press 'Enter' to exit";
   cin.ignore();

   return 0;
   }

Download HumanInteractions4.zip - 9KB

When you execute this program, notice the "Néih hóu":

The objects did talk to each other, but something got pretty messed up in translation!

The problem boils down to character sets, and how different languages are represented with 'ones' and 'zeros' in a computer's memory. I won't touch upon this subject, except to give a solution (without much explanation) to solve the issue.

Google, Bing, DuckDuckGo, and the other search engines are definitely one of a programmer's most used tools. A search for "visual studio console wstring" bring up this StackOverflow page, and it gives one method to circumvent the problem.

Experience told me that part of the issue was due to using std::string instead of std::wstring, which is why I chose that search term. To fix the issue, the program needs to be rewritten as follows:

//HumanInteractions5:
#include <string>
#include <iostream>
#include <io.h>
#include <fcntl.h>

using namespace std;


class Human {
   protected:
      wstring nameC;

   public:
      Human(const wstring & name) : nameC(name) { }

      wstring name() const { return nameC; }

      void talkTo(const Human & person) const {
         wcout << nameC << " says: Hello, " << person.name() << "!" << endl;
         }

   };


class ChinesePerson : public Human {
   public:
      ChinesePerson(const wstring & name) : Human(name) { }

      void talkTo(const Human & person) const {
         wcout << nameC << " says: Néih hóu, " << person.name() << "!" << endl;
         }
   };


int main() {
   _setmode(_fileno(stdout), _O_U16TEXT);
   Human joe(L"Joe");   //The "L" tells the compiler to treat the text as a wide string
   ChinesePerson li(L"Li");

   li.talkTo(joe);

   //And 'joe' can talk to 'li':
   joe.talkTo(li);
   
   wcout << endl << "Press 'Enter' to exit";
   cin.ignore();

   return 0;
   }

Download HumanInteractions5.zip - 9KB

When executed, the expected result is obtained:

Unfortunately we cannot use true Chinese characters without a lot more effort (as far as I know), so I will leave it at this textual state.

(Actually, looking at it a little more, possibly not so much is required. It seems all you would have to do is hack the registry so that consoles could use a font like Arial Unicode MS. If you ever want to play with that some more, I believe these are the characters for this instance: 你好. Now let's get back to work!)

Finally! The Introductions!

At last we have the background material to begin solving our 'talking' problem. Since I have not yet shown a virtual example, let me first extend the previous to illustrate how the problem could be solved without it. We will fill in that long if loop, and make some more modifications. Note that I have removed the talkTo routine from the Human class, in order to emphasize that each type of culture is doing its own 'talking.' I have also showed what it takes to iterate through a list, and talk that way:

//HumanInteractions6:
#include <string>
#include <iostream>
#include <io.h>
#include <fcntl.h>
//New introduction: vector:
#include <vector>

using namespace std;


//The following enum is a way to associate a culture of a person to a number,
//so the computer can quickly look it up:

enum class Culture {
   Chinese = 1,   //You can set an enum value to some, or all elements like this
   American,      //If you don't set the value, the compiler will do so for you.
                  //'American' will probably be set to '2' automatically.
   Unknown        //And this one will be set to '3', but it doesn't matter to you,
                  //because all that is cared about is they are different values.
                  //(You can set two values to the same number, but don't expect
                  //it to work the way you are expecting without a lot more effort.
                  //For example, change 'American' to 'American = 1' without changing
                  //the Chinese definition. That change can never work as expected.)
   };


class Human {
   protected:
      wstring nameC;
      //Note the addition of the following 'Culture' variable:
      Culture cultureC;

   public:
      //The 'Human' constructor now requires the culture of the person to be given:
      Human(const wstring & name, Culture culture) : nameC(name), cultureC(culture) { }
      
      //If you are asking, 'culture' is passed 'by value' and 'name' is passed 'by reference'
      //in the previous constructor because passing variables that are the size of the
      //computer's registers is more efficient than passing the address to that variable
      //and then looking it up inside the function.
      //
      //On the other hand, in the case of the 'name' it is much faster to deal with addresses
      //than it is to construct a temporary string and pass it, because the string is
      //much bigger than the size of a register, and requires construction, and later,
      //destruction.
      //
      //There are cases in which you would pass a reference (or pointer) to something small,
      //like 'culture,' into a function, but they aren't very common. Specifically, if you
      //needed to change the value in the called function, and have the calling function
      //then use the changed value, you could do so. But usually in those cases it is better
      //to make the variable part of the class itself, so it wouldn't need to be passed.
      
      wstring name() const { return nameC; }

      //And the following allows you to retrieve the culture of the person:
      Culture culture() const { return cultureC; }
   };


class AmericanPerson : public Human {
   public:
      //The 'AmericanPerson' constructor now passes the correct enumeration to 'Human'
      AmericanPerson(const wstring & name) : Human(name, Culture::American) { }

      void talkTo(const Human & person) const {
         wcout << nameC << " says: Hello, " << person.name() << "!" << endl;
         }
   };


class ChinesePerson : public Human {
   public:
      //And the ChinesePerson's culture is set like this:
      ChinesePerson(const wstring & name) : Human(name, Culture::Chinese) { }

      void talkTo(const Human & person) const {
         wcout << nameC << L" says: Néih hóu, " << person.name() << L"!" << endl;
         }
   };


int main() {
   _setmode(_fileno(stdout), _O_U16TEXT);
   AmericanPerson joe(L"Joe");
   ChinesePerson li(L"Li");

   //We can still do the talking the original way, even though 'Human' no longer has a
   //'talkTo' function:
   li.talkTo(joe);
   joe.talkTo(li);

   //But if we wanted to iterate through a list of Humans, and do the talking, we have
   //to put in checks for the culture, as an earlier code snippet indicated:
   
   wcout << endl << "Now talking by iterating over the list:" << endl << endl;

   //First, create a list of people. One good library tool for this is a 'vector.'
   //The following line creates a vector of pointers to our Humans:
   vector<Human*> people;

   //And the following puts pointers to our people on that vector:
   people.push_back(&li);
   people.push_back(&joe);

   //Now, for the talking.  I will do it with a series of 'for' loops that
   //iterate over the speaker and listeners. I don't expect you to understand
   //the loops, but you can use them as a springboard for deeper investigation:
   
   for (int speaker=0, numPeople=people.size(); speaker<numPeople; ++speaker) {

      for (int listener=0; listener<numPeople; ++listener) {

         if (listener != speaker) { //Nobody talks to themselves
      
            Culture culture = people[speaker]->culture();
            
            if (culture == Culture::American) {
               //(I don't expect you to understand the 'static_cast', and other items
               //in the following lines, either, but they can lead to more C++ learning.)
               
               AmericanPerson * ap = static_cast<AmericanPerson*>(people[speaker]);
               ap->talkTo(*people[listener]);
               }
            
            else if (culture == Culture::Chinese) {
               ChinesePerson * cp = static_cast<ChinesePerson*>(people[speaker]);
               cp->talkTo(*people[listener]);
               }

            else {
               wcout << "Culture unknown - programming error" << endl;
               }
            }
         }
      }

   
   wcout << endl << "Press 'Enter' to exit";
   cin.ignore();

   return 0;
   }

Download HumanInteractions6.zip - 9KB

The program is not so small anymore, but it shows the complexity of coding without virtual functions, and does what is desired:

The issue we now have is we wish to iterate through a list of Humans, and have each one 'talk' in their turn, but the 'talking' function is dependent upon the type of Human doing the talking. What we really want is a generic talkTo routine that the sub-classes can implement however they wish, and that the computer can call automatically based on type.

This is where virtual functions come in. Public routines (or protected routines, for that matter) marked virtual can be overridden by the sub-classes, and when they are called through a pointer to the base class in which the function is declared, the program will figure out the correct function to call behind the scenes. I will add a few more types of Humans, and solve the initial problem, and do so without the Culture enum of the previous approach:

//HumanInteractions7:
#include <string>
#include <iostream>
#include <vector>
#include <io.h>
#include <fcntl.h>

using namespace std;


class Human {
   protected:
      wstring nameC;

   public:
      Human(const wstring & name) : nameC(name) { }

      wstring name() const { return nameC; }

      virtual void talkTo(const Human & person) const { }
      //The previous 'talkTo' definition could have forced every sub-class of 'Human'
      //to implement 'talkto' for themselves by adding a "= 0" towards the end of the
      //statement, like:
      //virtual void talkTo(const Human & person) const = 0 { }

      //As it is, if a sub-class doesn't implement it, this "{ }" do-nothing implentation
      //will be called, and that person won't appear to 'talk'.

      //Adding "= 0" makes the class into a "Pure Virtual" class, which can't be
      //instantiated by itself.  This is useful for some situations.
   };


class AmericanPerson : public Human {
   public:
      AmericanPerson(const wstring & name) : Human(name) { }

      //The 'override' in the following definition allows the compiler to let us know if we
      //have mistyped the name of the function, or its arguments, and it doesn't match a
      //function defined in a base class. Without 'override' it is MUCH more difficult to
      //find typos of that nature.
      
      void talkTo(const Human & person) const override {
         wcout << nameC << " says: Hello, " << person.name() << "!" << endl;
         }
   };


class ChinesePerson : public Human {
   public:
      ChinesePerson(const wstring & name) : Human(name) { }

      void talkTo(const Human & person) const override {
         wcout << nameC << L" says: Néih hóu, " << person.name() << L"!" << endl;
         }
   };


class MexicanPerson : public Human {
   public:
      MexicanPerson(const wstring & name) : Human(name) { }

      void talkTo(const Human & person) const override {
         wcout << nameC << L" says: ¡Hola!, " << person.name() << L"!" << endl;
         }
   };


class GhettoSlanger : public Human {
   public:
      GhettoSlanger(const wstring & name) : Human(name) { }

      void talkTo(const Human & person) const override {
         wcout << nameC << L" says: Yo, Homey! Whassup?" << endl;
         }
   };


class JapanesePerson : public Human {
   public:
      JapanesePerson(const wstring & name) : Human(name) { }

      void talkTo(const Human & person) const override {
         wcout << nameC << L" says: Konnichiwa, " << person.name() << L"!" << endl;
         }
   };


int main() {
   _setmode(_fileno(stdout), _O_U16TEXT);

   AmericanPerson joe(L"Joe");
   ChinesePerson li(L"Li");
   MexicanPerson jose(L"Jose");
   GhettoSlanger mark(L"Mark");
   JapanesePerson hana(L"Hana");

   vector<Human*> people;
   people.push_back(&joe);
   people.push_back(&li);
   people.push_back(&jose);
   people.push_back(&mark);
   people.push_back(&hana);

   for (int speaker=0, numPeople=people.size(); speaker<numPeople; ++speaker) {
      for (int listener=0; listener<numPeople; ++listener) {
         if (speaker != listener) {
            people[speaker]->talkTo(*people[listener]);
            }
         }
      wcout << endl;
      }
   
   wcout << "Press 'Enter' to exit";
   cin.ignore();

   return 0;
   }

Download HumanInteractions7.zip - 9KB

And with that, the solution is complete. You have seen how to create objects in C++, and how virtual functions allow you to eliminate logic checks in many circumstances. What I have just showed you is at the heart of C++ Object Orientation, and is very useful in many circumstances. I hope this helps you achieve a modicum of mastery much faster than I was able to.

Before wrapping this up, I will mention that you will often see items being created through new. Doing so would have eliminated some of the code in the previous main function. new creates an object somewhere in the computer's 'heap' memory, to give you another term for further study. It then returns the address to that position (in the form of a pointer) to you.

Knowing that, a first iteration would be the following, but it is incredibly naive, and you should NEVER DO IT! (Sorry for shouting, but it's kind of important.)

int main() {
   _setmode(_fileno(stdout), _O_U16TEXT);

   vector<Human*> people;
   people.push_back(new AmericanPerson(L"Joe"));   //This stores a copy of the pointer
                                                   //into the vector
   people.push_back(new ChinesePerson (L"Li"));
   people.push_back(new MexicanPerson (L"Jose"));
   people.push_back(new GhettoSlanger (L"Mark"));
   people.push_back(new JapanesePerson (L"Hana"));

   for (int speaker=0, numPeople=people.size(); speaker<numPeople; ++speaker) {
      for (int listener=0; listener<numPeople; ++listener) {
         if (speaker != listener) {
            people[speaker]->talkTo(*people[listener]);
            }
         }
      wcout << endl;
      }
   
   wcout << "Press 'Enter' to exit";
   cin.ignore();

   return 0;
   }

The solution eliminates the need to address each person with a variable name, and the solution is shorter than the previous code, but the problem is that none of the objects will be deleted at the end of the scope this way. The earlier solutions always ended up calling what is called the destructor of the objects, but this one doesn't.

One solution for that is to modify the program to this:

//First, we need to modify the 'Human' class by giving it a virtual destructor,
//so the program will know that it needs to look up the correct destructor when it is called.
//The only change is the addition of one line:

class Human {
   protected:
      wstring nameC;

   public:
      Human(const wstring & name) : nameC(name) { }

      //New line:
      virtual ~Human() { }
      
      //In all of the previous examples the compiler created destructors for us without
      //any input on our part. Now we are taking control because the compiler
      //doesn't know what we need, but we do, and need to tell it about our requirement.

      wstring name() const { return nameC; }

      virtual void talkTo(const Human & person) const { }
   };


//And 'main' needs to be modified like this:

int main() {
   _setmode(_fileno(stdout), _O_U16TEXT);

   vector<Human*> people;
   people.push_back(new AmericanPerson(L"Joe"));
   people.push_back(new ChinesePerson (L"Li"));
   people.push_back(new MexicanPerson (L"Jose"));
   people.push_back(new GhettoSlanger (L"Mark"));
   people.push_back(new JapanesePerson (L"Hana"));

   for (int speaker=0, numPeople=people.size(); speaker<numPeople; ++speaker) {
      for (int listener=0; listener<numPeople; ++listener) {
         if (speaker != listener) {
            people[speaker]->talkTo(*people[listener]);
            
            //By the way, we could change all the 'talkTo' functions to take pointers
            //instead of references, so the previous line would be a little cleaner,
            //without the "*":
            //"people[speaker]->talkTo(people[listener]);"
            //But then you would need to place a check in each of the functions to make
            //certain the pointer was valid, which looks like this if the pointer was named
            //'human': "if (human) { doTheTalking; }"
            }
         }
      wcout << endl;
      }
   
   wcout << "Press 'Enter' to exit";
   cin.ignore();

   while (!people.empty()) {
      delete people.back();
      people.pop_back();
      }

   return 0;
   }

Download HumanInteractions8.zip - 9KB

Now, at the end, the objects will be deleted. But even this solution is not the best, because if bad things happen between the "people.push_back(new JapanesePerson (L"Hana"));" line and the "while (!people.empty()) {" line, the "while (!people.empty()) {" line would never be reached, and the objects would be 'leaked,' as before.

The current best paradigm for solving this problem is to use some helper objects from the standard library. I won't go into the details - you can search online for the words used in the the next code block. When you see it in other people's work you will be better prepared to understand it from having seen it in this rewrite of our main function:

//HumanInteractions9:
//This approach requires another standard library file to be included.
//You can add it at the very top of the file, or you can even add it right before
//'main', as is done here.  The first is a better option, though, because it allows you
//to more easily see what is required for the entire file, instead of having to search
//around.
#include <memory>  //for 'unique_ptr'

int main() {
   _setmode(_fileno(stdout), _O_U16TEXT);

   //Create a vector of std::unique_ptr to Humans:
   vector<unique_ptr<Human>> people;

   //And populate it:
   people.push_back(make_unique<AmericanPerson>(L"Joe"));
   people.push_back(make_unique<ChinesePerson>(L"Li"));
   people.push_back(make_unique<MexicanPerson>(L"Jose"));
   people.push_back(make_unique<GhettoSlanger>(L"Mark"));
   people.push_back(make_unique<JapanesePerson>(L"Hana"));

   //Now iterate through everyone, and have them greet each other:
   for (int speaker=0, numPeople=people.size(); speaker<numPeople; ++speaker) {
      for (int listener=0; listener<numPeople; ++listener) {
         if (speaker != listener) {
            people[speaker]->talkTo(*people[listener]);
            }
         }
      wcout << endl;
      }
   
   wcout << "Press 'Enter' to exit";
   cin.ignore();

   //Note: We don't need to delete anything with this approach. It occurs automatically.
   //We DO continue to need the virtual destructor with this approach, which is something
   //I learned that required a secondary modification to this article.

   return 0;
   }

Download HumanInteractions9.zip - 9KB

And that is everything I wish to share in this article, since my fingers are tired, and it's time to do something else. This writing has summarized many years of work and learning, and hopefully helps some of you grasp the concepts a little more easily than I did. I know I never covered header files, and a couple other items I brought up, but I will leave that for another edition/iteration, if it is called for. The previous concepts are far more important than the items I've left out.

Thanks for reading, and Happy Coding!

History

  • November 24, 2014:
    • In HumanInteractions9, everything I'd read gave me the impression that the unique_ptrs would keep track of type, so the correct destructors would be called without a virtual destructor. But intuition kicked in (of course, after the article was already public : rolleyes : ), so I tested. My conclusion was incorrect (the sources I read were probably correct - I just misread them). Therefore I added a virtual destructor back to the Human class. I apologize for misleading earlier readers of this article, and hope my misstatement is made known to them so they don't create leaky code as a result of the earlier versions.
    • Corrected a comment about memory locations in HumanInteractions2.
    • Broke up a heavy paragraph, and other minor edits.
    • Changed recommendation from Visual Studio Express to Visual Studio Community Edition, which was made available after the first edition of this article was made public. Of course, if you want a smaller install that doesn't take up as much disk space, the Express Edition would be the way to go, but the Community Edition gives you more options that are worth the added space in my opinion.
    • Improved const correctness in all the code, in order to set a better example.
  • November 4, 2014: Added virtual destructor to what is now HumanInteractions8 example, which I overlooked in earlier version. (HumanInteractions9 was previously HumanInteractions8.)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

David O'Neil
Software Developer www.randommonkeyworks.com
United States United States
I am the author of Laughing at the Devil: One Man’s Religious Discoveries. For those who are “ready to look at the world - religion, science, spirituality - differently,” LATD is the book to turn to.
 
In about 1994 I began studying and documenting the astronomy of our ancestors. A hint lead to many years of partial understanding, before a profound breakthrough occurred and some old myths finally made sense.
 
The greatest of my discoveries is the celestial observations behind the biblical tale of Samson, which was created 3,000 years ago. That find casts a profound new light on the roots of Western religion, as well as the foundation of modern science. To learn more, visit my website.
 
Trained as a mechanical engineer, I learned C++ programming on my own in order to create a MIDI program. I am delighted to say I also succeeded in that goal. Happy coding, everybody!

Comments and Discussions

 
QuestionThanks [modified] PinmemberDavid O'Neil24-Nov-14 9:45 
GeneralMy vote of 5 PinmemberAndres Cassagnes31-Oct-14 9:06 
GeneralRe: My vote of 5 PinmemberDavid O'Neil31-Oct-14 10:28 
QuestionWhy not using cultures to localize greetings? PinmemberMarius Bancila30-Oct-14 23:26 
AnswerRe: Why not using cultures to localize greetings? PinmemberMarius Bancila31-Oct-14 4:36 
AnswerRe: Why not using cultures to localize greetings? PinmemberDavid O'Neil31-Oct-14 10:27 
GeneralMy vote of 4 PinmemberRomualdas Cukuras30-Oct-14 21:40 
GeneralRe: My vote of 4 PinmemberDavid O'Neil31-Oct-14 10:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 24 Nov 2014
Article Copyright 2014 by David O'Neil
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid