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

330 comments to 8.x — Chapter 8 comprehensive quiz

  • Hi Alex!

    Solution 3g doesn't use the same `getRandomNumber` as shown in lesson 5.9

  • Anastasia

    I haven't finished this chapter yet, just wanted to get to the quiz as soon as possible :D
    3-all

    Monster.h

    Monster.cpp

    MonsterGenerator.h

    MonsterGenerator.cpp

    getRandomNumber.cpp

    main.cpp

    I don't have particular questions, but I'm worried a bit that I might have messed something up with all those includes and declarations.

    • - Pass/return non-fundamental types by (const) reference.
      - Initialize your variables with brace initializers.

      "the " shouldn't be part of `getTypeString`, but instead be added in `print`. Doing so increases reusability of `getTypeString` and reduces repetitions.

      > I might have messed something up with all those includes
      You should add a header for "getRandomNumber.cpp" so that "MonsterGenerator.cpp" doesn't have to use forward declarations. Otherwise your includes are alright :)

      • Anastasia

        > Pass/return non-fundamental types by (const) reference.
        > "the " shouldn't be part of `getTypeString`
        Fixed, thank you.

        > Initialize your variables with brace initializers.
        in `MonsterGenerator.cpp` line 18-24 should be `Monster { ... }`. And everything in `getRandomNumber.cpp` as well :/ Is that it?

        > You should add a header for "getRandomNumber.cpp"
        But I thought we should prefer using forward declarations if including header can be avoided.

        edit:wrong file name fixed. The anonymous Monster lives in `MonsterGenerator.cpp`, not in `Monster.cpp`

        • > Initialize your variables with brace initializers
          MonsterGenerator.cpp:14: `return { /**/ };`. Brace initialization knows the return type.
          And everything in "getRandomNumber.cpp".

          > I thought we should prefer using forward declarations if including header can be avoided
          In a header, forward declaring a type is better (I wouldn't forward declare a function, as they change quite often). In a source file, it doesn't matter.
          Forward declarations protect you from circular includes. Since you don't include .cpp files, you don't have to worry about includes therein.

  • Hi, would you help me understand why the code below does not shuffle. I am using the mersenne twister generator, so whenever I call getRandomNumber() in shuffleDeck() member function, the deck is not being shuffled. I would think that for every index of the for loop, a new random number(RN) will be generated but, it is not the case.  Also, getRAndomNumber static prefix is not the reason for this.

    I ended up seeding from a namespace or directly in shuffleDeck(). This one works however, but i'm trying to understand the first option. Thank

    • You're seeding the twister with every call to `getRandomNumber`. Since you're seeding it with the same number every time, you'll get the same "random" numbers.
      Seed random number generators only once.

  • A

    Hello! This is my code for the first portion of the quiz:

    main.cpp:

    Monster.h:

    Monster.cpp:

    MonsterGenerator.h:

    MonsterGenerator.cpp:

    • Hello!

      - Use std::srand instead of its global-namespaced counterpart.
      - Limit your lines to 80 characters in length for better readability.
      - Pass/return non-fundamental types by (const) reference. Copying them is slow.
      - `Monster::getTypeString`: Missing return-statement. Enable compiler warnings.
      - You're using the same name style for variables and functions, this can lead to confusion.
      - `MonsterGenerator::getRandomNumber::fraction`: Good use of `constexpr`.
      - MonsterGenerator.cpp:18: Use brace initialization. `return { randType, ... }`.

      • A

        Thank you for the response!

        - Is MonsterGenerator.cpp:18 an instance where I would return by const reference?
        - I use Xcode, and as long as all the cases are handled, the compiler doesn’t complain about a return statement—I have warnings on, but do I need to change something?
        - Could you point out an example of where my variable naming style is similar to that of a function’s/how I could alter it?
        - On line MonsterGenerator.cpp:18, why would I use brace initialization to reference a class construct?

        • A

          Regarding the last bullet point, I didn't previously realize you could use initialization in that context, so please ignore that.

        • > Is MonsterGenerator.cpp:18 an instance where I would return by const reference?
          No, you'd return a reference to a local temporary. Doing this causes and dangling reference and thus undefined behavior.

          > as all the cases are handled, the compiler doesn’t complain
          If `m_type` is not an enumerator of `MonsterType` (This can happen by having gaps in the enum or by casting), the function won't return and cause undefined behavior.

          > variable naming style
          "generateMonster" Function
          "randType" Variable
          "MonsterType" Type
          You're using camel case with an upper case first letter for types, that's good.
          But you're using lower camel case for functions and variables.
          I use hungarian notation for variables. You can use that, or use underscores as a word delimiter, or think of something else. As long as it's readable and consistent, it's up to you what you use.

  • B0PE

    Hi,
    I would like to know if it is OK to include "Card.h" into "Deck.h", or if this is bad programing.
    Or are there other ways to let class Deck know the variables of class Card?

    Sorry if this is written somewhere, but I couldn't find it.

    Greetings B0PE

    • Yes. If "Deck.h" needs whatever is declared in "Card.h", include it.
      If "Deck.h" only needs a forwards declarations (ie. it only uses a reference of pointer of a type declared in "Card.h"), use a forward declaration instead.

  • Dany

    Hello. I think it would be good idea to put game logic into another class.
    Do something like that, because players don't want to create deck manually and shuffle it, they just want to hit play.

    • - Initialize your variables with brace initializers.
      - Limit your lines to 80 characters in length for better readability on small displays.
      - Use std::rand instead of its global-namespaced counterpart.
      - Use std::srand instead of its global-namespaced counterpart.
      - Use std::time instead of its global-namespaced counterpart.
      - `std::time` wants a pointer, pass `nullptr`.
      - `getPlayerChoice` should return a `bool` to prevent a duplicate comparison.
      - `while` wants a `bool`. Use `while (true)`.

      Moving the game into it's own class is good.

      • Dany

        Thank you, 'getPlayerChoice' became a little less obvious(so i added commentary) but i like it more than previous one.

  • Dimbo1911

    Good morning! :)
    Does this code seem ok (for the first part of the quiz)?

    • - Use std::rand instead of its global-namespaced counterpart.
      - Use std::srand instead of its global-namespaced counterpart.
      - Use std::time instead of its global-namespaced counterpart.
      - `fraction` should be `constexpr`.
      - Use double literals for doubles (1.0 instead of 1).
      - Initialize your variables with brace initializers (Line 96).
      - Line 80, 88 and 98, 99: These numbers depend on each other, add constants or use `std::size`.
      - Name your variables descriptively. Avoid abbreviations. "rmt", "a" are meaningless to anyone reading your code.
      - Pass non-built-in types by (const) reference (`std::string`).
      - "MonsterType", "getMonsterType" and "generateMonster" are redundant names, because they're members of classes that already indicate "Monster" (Some, or all, of these were given by Alex, so it's not your fault).
      - `MonsterType` doesn't have to be `enum class`, because it's nested in a class, preventing name conflicts.
      - `std::time` wants a pointer as its argument. Pass `nullptr`.

  • DEEPAK

    hello,
    why we make return value as reference, just to prevent copying or for something else?  

  • Chris

    Hey, was wondering why I get the following error when I don't enter parameters when instantiating a card object and  calling its print member function. With parameters it works fine. I cannot find my silly mistake
    Error: request for member ‘printCard’ in ‘standard’, which is of non-class type ‘Card()’|

    Card.h

    Card.cpp

    main.cpp

    • * Initialize your variables with brace initializers.
      * Limit your lines to 80 characters in length for better readability on small displays.
      * Use std::rand instead of its global-namespaced counterpart.
      * Use std::srand instead of its global-namespaced counterpart.
      * Use std::time instead of its global-namespaced counterpart.

      main.cpp:12: That's a function prototype.

  • Aun

    ** point2d.h

    ** point2d.cpp

    ** main.cpp
    is from this homework

    error:

    /home/aun/Documents/cpp/chap7/quizch8/src/point2d.cpp: In function ‘double distance(const Point2d&)’:
    /home/aun/Documents/cpp/chap7/quizch8/src/point2d.cpp:18:15: error: ‘m_x’ was not declared in this scope
      return sqrt((m_x - other.m_x)*(m_x - other.m_x) + (m_y - other.m_y)*(m_y - other.m_y));

  • Paulo Filipe

    Hey guys, is this the correct way of passing a vector of pointers to an object, to a function, so that we can manipulate that same vector without creating ya copy of it?

    pCards is a vector of pointers to object Card. My goal is to pass it to a function and be able to resize the vector assign new Card objects to this vector without creating a copy of it.

    Regards!

  • Jeff

    How do I convert class Deck into a split file (header and cpp)? All I got is this:

    Deck.h

    Deck.cpp

    Source.cpp

    I'm not able to initialize the deck.

    • Jeff

      In case you wanted to see the Card header and cpp files as well.

      Card.h

      Card.cpp