Search

8.x — Chapter 8 comprehensive quiz

In this chapter, we explored the meat of C++ -- object-oriented programming! This is the most important chapter in the tutorial series.

Chapter summary

Classes allow you to create your own data types that bundle both data and functions that work on that data. Data and functions inside the class are called members. Members of the class are selected by using the . operator (or -> if you’re accessing the member through a pointer).

Access specifiers allow you to specify who can access the members of a class. Public members can be accessed directly by anybody. Private members can only be accessed by other members of the class. We’ll cover protected members later, when we get to inheritance. By default, all members of a class are private and all members of a struct are public.

Encapsulation is the process of making all of your member data private, so it can not be accessed directly. This helps protect your class from misuse.

Constructors are a special type of member function that allow you to initialize objects of your class. A constructor that takes no parameters (or has all default parameters) is called a default constructor. The default constructor is used if no initialization values are provided by the user. You should always provide at least one constructor for your classes.

Member initializer lists allows you to initialize your member variables from within a constructor (rather than assigning the member variables values).

In C++11, non-static member initialization allows you to directly specify default values for member variables when they are declared.

Prior to C++11, constructors should not call other constructors (it will compile, but will not work as you expect). In C++11, constructors are allowed to call other constructors (called delegating constructors, or constructor chaining).

Destructors are another type of special member function that allow your class to clean up after itself. Any kind of deallocation or shutdown routines should be executed from here.

All member functions have a hidden *this pointer that points at the class object being modified. Most of the time you will not need to access this pointer directly. But you can if you need to.

It is good programming style to put your class definitions in a header file of the same name as the class, and define your class functions in a .cpp file of the same name as the class. This also helps avoid circular dependencies.

Member functions can (and should) be made const if they do not modify the state of the class. Const class objects can only call const member functions.

Static member variables are shared among all objects of the class. Although they can be accessed from a class object, they can also be accessed directly via the scope resolution operator.

Similarly, static member functions are member functions that have no *this pointer. They can only access static member variables.

Friend functions are functions that are treated like member functions of the class (and thus can access a class’s private data directly). Friend classes are classes where all members of the class are considered friend functions.

It’s possible to create anonymous class objects for the purpose of evaluation in an expression, or passing or returning a value.

You can also nest types within a class. This is often used with enums related to the class, but can be done with other types (including other classes) if desired.

Quiz time

1a) Write a class named Point2d. Point2d should contain two member variables of type double: m_x, and m_y, both defaulted to 0.0. Provide a constructor and a print function.

The following program should run:

This should print:

Point2d(0, 0);
Point2d(3, 4);

Show Solution

1b) Now add a member function named distanceTo that takes another Point2d as a parameter, and calculates the distance between them. Given two points (x1, y1) and (x2, y2), the distance between them can be calculated as sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)). The sqrt function lives in header cmath.

The following program should run:

This should print:

Point2d(0, 0);
Point2d(3, 4);
Distance between two points: 5

Show Solution

1c) Change function distanceTo from a member function to a non-member friend function that takes two Points as parameters. Also rename it “distanceFrom”.

The following program should run:

This should print:

Point2d(0, 0);
Point2d(3, 4);
Distance between two points: 5

Show Solution

2) Write a destructor for this class:

Show Solution

3) Let’s create a random monster generator. This one should be fun.

3a) First, let’s create an enumeration of monster types named MonsterType. Include the following monster types: Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, and Zombie. Add an additional MAX_MONSTER_TYPES enum so we can count how many enumerators there are.

Show Solution

3b) Now, let’s create our Monster class. Our Monster will have 4 attributes (member variables): a type (MonsterType), a name (std::string), a roar (std::string), and the number of hit points (int). Create a Monster class that has these 4 member variables.

Show Solution

3c) enum MonsterType is specific to Monster, so move the enum inside the class as a public declaration.

Show Solution

3d) Create a constructor that allows you to initialize all of the member variables.

The following program should compile:

Show Solution

3e) Now we want to be able to print our monster so we can validate it’s correct. To do that, we’re going to need to write a function that converts a MonsterType into a std::string. Write that function (called getTypeString()), as well as a print() member function.

The following program should compile:

and print:

Bones the skeleton has 4 hit points and says *rattle*

Show Solution

3f) Now we can create a random monster generator. Let’s consider how our MonsterGenerator class will work. Ideally, we’ll ask it to give us a Monster, and it will create a random one for us. We don’t need more than one MonsterGenerator. This is a good candidate for a static class (one in which all functions are static). Create a static MonsterGenerator class. Create a static function named generateMonster(). This should return a Monster. For now, make it return anonymous Monster(Monster::SKELETON, “Bones”, “*rattle*”, 4);

The following program should compile:

and print:

Bones the skeleton has 4 hit points and says *rattle*

Show Solution

3g) Now, MonsterGenerator needs to generate some random attributes. To do that, we’ll need to make use of this handy function:

However, because MonsterGenerator relies directly on this function, let’s put it inside the class, as a static function.

Show Solution

3h) Now edit function generateMonster() to generate a random MonsterType (between 0 and Monster::MAX_MONSTER_TYPES-1) and a random hit points (between 1 and 100). This should be fairly straightforward. Once you’ve done that, define two static fixed arrays of size 6 inside the function (named s_names and s_roars) and initialize them with 6 names and 6 sounds of your choice. Pick a random name from these arrays.

The following program should compile:

Show Solution

3i) Why did we declare variables s_names and s_roars as static?

Show Solution

4) Okay, time for that game face again. This one is going to be a challenge. Let’s rewrite the Blackjack games we wrote in chapter 6 using classes! Here’s the full code without classes:

Holy moly! Where do we even begin? Don’t worry, we can do this, but we’ll need a strategy here. This Blackjack program is really composed of four parts: the logic that deals with cards, the logic that deals with the deck of cards, the logic that deals with dealing cards from the deck, and the game logic. Our strategy will be to work on each of these pieces individually, testing each part with a small test program as we go. That way, instead of trying to convert the entire program in one go, we can do it in 4 testable parts.

Start by copying the original program into your IDE, and then commenting out everything except the #include lines.

4a) Let’s start by making Card a class instead of a struct. The good news is that the Card class is pretty similar to the Monster class from the previous quiz question. First, move the enums for CardSuit, CardRank inside the card class as public definitions (they’re intrinsically related to Card, so it makes more sense for them to be inside the class, not outside). Second, create private members to hold the CardRank and CardSuit (name them m_rank and m_suit accordingly). Third, create a public constructor for the Card class so we can initialize Cards. Forth, make sure to assign default values to the parameters so this can be used as a default constructor (pick any values you like). Finally, move the printCard() and getCardValue() functions inside the class as public members (remember to make them const!).

Important note: When using a std::array (or std::vector) where the elements are a class type, your element’s class must have a default constructor so the elements can be initialized to a reasonable default state. If you do not provide one, you’ll get a cryptic error about attempting to reference a deleted function.

The following test program should compile:

Show Solution

4b) Okay, now let’s work on a Deck class. The deck needs to hold 52 cards, so use a private std::array member to create a fixed array of 52 cards named m_deck. Second, create a constructor that takes no parameters and initializes m_deck with one of each card (modify the code from the original main() function). Inside the initialization loop, create an anonymous Card object and assign it to your deck element. Third, move printDeck into the Deck class as a public member. Fourth, move getRandomNumber() and swapCard() into the Deck class as a private static members (they’re just helper functions, so they don’t need access to *this). Fifth, move shuffleDeck into the class as a public member.

Hint: The trickiest part of this step is initializing the deck using the modified code from the original main() function. The following line shows how to do that.

The following test program should compile:

Show Solution

4c) Now we need a way to keep track of which card is next to be dealt (in the original program, this is what cardptr was for). First, add a int member named m_cardIndex and initialize it to 0. Create a public member function named dealCard(), which should return a const reference to the current card and advance the index. shuffleDeck() should also be updated to reset m_cardIndex (since if you shuffle the deck, you’ll start dealing from the top of the deck again).

The following test program should compile:

Show Solution

4d) Almost there! Now, just fix up the remaining program to use the classes you wrote above. Since most of the initialization routines has been moved into the classes, you can jettison them.

Show Solution

9.1 -- Introduction to operator overloading
Index
8.16 -- Timing your code

219 comments to 8.x — Chapter 8 comprehensive quiz

  • Louis Cloete

    I have a question. I use Code::Blocks 17.12 with the GNU GCC compiler. However, I also had to discard the first value returned by rand() to get my answer to Quiz 3 to behave properly. Why is that? I understood that is a problem only affecting Visual Studio. I am attaching the program code as well as a batch file running it 1 000 times. Install the .bat file in the same directory as the build directory and then run the .bat file. Compare the results if you run it with and without the commented out line in main().

    8_x_Q3_RandomMonsterGenerator.cpp, resulting in 8_x_Q3_RandomMonsterGenerator.exe

    Batch file:

    Thanks in advance.
    Louis

  • Somebody62

    Referring to question 3g, wouldn't this be a simpler function to find a random number?

    However, this function might not be perfectly weighted, so you might favor the old one.

    Thank you!

  • Sachees

    I cannot compile the Blackjack code from question 4(the one mentioned before 4a). I use visual studio and the compiler tells me that... "cout" isn't member of "std"! I copied the code directly from website and I added >>#include "stdafx.h"<<.

    • Somebody62

      This message is usually caused by forgetting "#include <iostream>" (without the quotation marks) at the top of the file. Please check to see if you forgot it or maybe it didn't get copied in.

      • Sachees

        This wasn't the case. I didn't expect that "#include "stdafx.h"" has to be in the first line of program, before everything else. Now it works.

  • Jack

    Noticed that, for quiz 3, the enum has all the MonsterTypes capitalised as if they were proper nouns (Ogre, Dragon, etc.), yet a couple of lessons ago in 'Nested Types in Classes' you had them in all caps (APPLE, BANANA, etc,). Is this a case of personal preference, or is there a standard/good practice that should be followed? I would have assumed all-caps is better as it is less likely to be confused for a class, which you mentioned should start with a capital letter?

    • nascardriver

      Hi Jack!

      There are many coding conventions. Google's C++ guideline suggests naming enumerators like constants and constants either all caps or starting with a lower case 'k' followed by camel case.

      References
      * https://google.github.io/styleguide/cppguide.html#Enumerator_Names

    • Alex

      For consistency I've capitalized them. However, because they live in the namespace of the class, they're unlikely to be confused for a class because nested classes are disallowed.

  • Liam

    Question 3f: What is the advantage of making MonsterGenerator its own class? Why shouldn't I make it a member function of class Monster? Or even just a standalone function?

    • Alex

      The advantage is mostly organizational. The process for generating a monster is different than the act of being a monster, therefore it makes some sense that these might be represented separately. That said, there's no strict rule here -- if the MonsterGenerator is trivial, you certainly could make it part of Monster itself. In this case, it's not trivial.

      I tend to use loose functions outside of a class when there is a collection of loosely related functions (e.g. a math or physics library). I tend to use static classes when I'm implementing something that has a specific job (in this case, generating monsters). There's some interesting discussion around this point here.

  • Serial Seth

    Why are the solutions to quiz question #1 passing the parameters by reference and while doing that adding the const type? Aren't we only wanting to to print 5? Wouldn't pass by value do the trick since both objects are created in main? What am I missing?

  • Nysnard

    How is it possible that sqrt() works even if I didn't include cmath? I am using Visual Studio 2017.

  • Cumhur

    here:

    if m_deck was not an array(assuming in std::array pointer is also carried), was an int value for example; we should have returned '*this' right?
    If we did, could we still return in constant?

    • Alex

      If I understand what you're asking correctly, no, you can't return *this, because *this refers to a Deck object, not a Card.

  • Dennis

    There seems to be a mistake with the solution for 4a. At line 93 we include iostream.
    This line should be at the very top of our program. Because class Card uses std::cout.
    Otherwise this program won't compile.

  • radu f

    Hi Alex,

    I wonder if adding a question like:
    - "considering the blackjack game program, how many classes do you think you should use to rewrite the code and why?"
    would be a good idea, before 4a-4d.

    Of course, there's nothing preventing us, the readers, to do that anyway...

    Thanks for your awesome tutorials.

    • Alex

      I didn't ask that question because it's hard to answer prescriptively. You should definitely have a Card class, and a Deck class. However, the Blackjack game itself could be a class, or not. There may be other classes or subclasses that could be created (I can't think of any, but maybe that's just my lack of imagination).

      I don't want users to get the impression that there's only one answer.

  • Matt

    This has been a lot of fun! I'm surprised, though, at how many more lines of code this took than the original implementation.  For this quiz I started with my own code from ch.6 instead of Alex's code on this page.

    By the way, @nascardriver, I found a way to add guidelines to Visual Studio.  It requires adding an extension, but I found it extremely helpful for this project:
    https://marketplace.visualstudio.com/items?itemName=PaulHarrington.EditorGuidelines#overview

    Alex, thanks for your awesome tutorials, and nascardriver, thanks all of your help along the way.  I never would have guessed I'd be able to write this code a couple months ago!

    blackjackConstants.h:

    Card.h:

    Card.cpp:

    Deck.h:

    Deck.cpp:

    Game.h:

    Game.cpp

    main.cpp:

    • nascardriver

      Hi Matt!

      I didn't look at your code in detail, because it really is a lot.

      * constexpr for your globals is good
      * I didn't read the task, @swapCard should only be there if the quiz asked you to, otherwise use std::swap.
      * The comments in your header files are good. You should start using documentation comments for those so they can be parsed by a documentation generator at some point. Some generators can probably parse normal comments, but the ones below are usually used.

      * The cases in @Game::deal are pretty similar but handling those in a single case will probably require you to change a lot of existing code.

  • Matt

    Hey Alex!

    Just some feedback on this chapter - since we are supposed to put our classes in separate .h and .cpp files, it would be extremely helpful if (at least for ch.8 where this is all new to us students) the examples and quiz solutions reflected correct multiple-file usage instead of single-file code which bypasses all of the intricacies.

    I understand it's more work on your end, and don't expect you to keep it up forever, but it definitely hasn't come natural to me and it's been very frustrating trying to sort out why my programs (with multiple files for classes) won't compile.  Through trial and error and frustration, I've managed to sort out all of the issues so far (which has teaching value of its own for sure), but when you're at the point of "I truly have no idea what's wrong..." it's nice to be able to peek at the solution and see where you've strayed from the path.

    I'm sure the multi-file paradigm is trivial to someone who has experience with it, but I don't think the one or two examples in lesson 8.9 provided enough variety to really demonstrate all of the nuances of the method.  Between #includes, forward declarations, keywords (like static), etc. it's difficult to grasp what goes in the .h and what goes in the .cpp.

    I am working on the random monster generator quiz now, for reference.

    Thanks for your time,
    Matt

    • Matt

      Here's my finished code for Quiz 3:

      MonsterGenerator.h

      MonsterGenerator.cpp

      Monster.h

      Monster.cpp

      main.cpp

      • nascardriver

        Hi Matt!

        Good job once again!

        * enum class is preferred over enum. Your enum is encapsulated in a class, so no problem here. I'd use the enum name too to access the enumerators to make clear which enum is being accessed. eg. VOID_TYPE is likely to occur in some other enum as well. You won't run into problems if you continue to encapsulate all enums, but what if suddenly someone joins the development team and they use a global enum?
        * uniform initialization in @getRandomNumber

        You don't need to define the constructor of @MonsterGenerator, you could even delete it or make it private.

        Monster.cpp
        Line 27: I'd go for an assert (or exception (Chapter 14)), because if this happens, something is wrong with your code.

  • Ran

    For 8.x.4.b:

    I just struck with the initialization of the deck. To me the
    following code should be fine, but the compiler tells me:

    =====================================================================
    Severity    Code    Description    Project    File    Line    Suppression State
    Error (active)    E1790    the default constructor of "std::array<Card, 52U>" cannot be referenced -- it is a deleted function    ConsoleApplication4    c:\Users\user\source\repos\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp    109    
    =====================================================================

    I create a class called Deck. Then I declare the private variable
    which is a array type of date with a name m_deck.

    After that, I use a constructor to initialize m_deck.

    I use the loop provided by Alex.

    Particularly, I use the trickiest code provided by Alex:

    To my best knowledge, it should work. But, I do not know why it is not
    work as I expected.

    • Ran

      After go back to the class Card:

      I found that there are somethings wrong with the constructor member initializer
      lists.

      The original constructor member initializer lists was written like
      this:

      Obviously, the default value should be assigned in the constructor's
      (). The following list should assign the class private members with
      the default value in the constructor's (). So, the correct constructor
      for the class Card should be written as:

      What a mistake!

  • Billy

    I know it's all over the place but somehow it works. Is there anything i can do to make it better/more efficient?