Search

9.x — Chapter 9 comprehensive quiz

In this chapter, we explored topics related to operator overloading, as well as overloaded typecasts, and topics related to the copy constructor.

Summary

Operator overloading is a variant of function overloading that lets you overload operators for your classes. When operators are overloaded, the intent of the operators should be kept as close to the original intent of the operators as possible. If the meaning of an operator when applied to a custom class is not clear and intuitive, use a named function instead.

Operators can be overloaded as a normal function, a friend function, or a member function. The following rules of thumb can help you determine which form is best for a given situation:

  • If you’re overloading assignment (=), subscript ([]), function call (()), or member selection (->), do so as a member function.
  • If you’re overloading a unary operator, do so as a member function.
  • If you’re overloading a binary operator that modifies its left operand (e.g. operator+=), do so as a member function if you can.
  • If you’re overloading a binary operator that does not modify its left operand (e.g. operator+), do so as a normal function or friend function.

Typecasts can be overloaded to provide conversion functions, which can be used to explicitly or implicitly convert your class into another type.

A copy constructor is a special type of constructor used to initialize an object from another object of the same type. Copy constructors are used for direct/uniform initialization from an object of the same type, copy initialization (Fraction f = Fraction(5,3)), and when passing or returning a parameter by value.

If you do not supply a copy constructor, the compiler will create one for you. Compiler-provided copy constructors will use memberwise initialization, meaning each member of the copy is initialized from the original member. The copy constructor may be elided for optimization purposes, even if it has side-effects, so do not rely on your copy constructor actually executing.

Constructors are considered converting constructors by default, meaning that the compiler will use them to implicitly convert objects of other types into objects of your class. You can avoid this by using the explicit keyword in front of your constructor. You can also delete functions within your class, including the copy constructor and overloaded assignment operator if desired. This will cause a compiler error if a deleted function would be called.

The assignment operator can be overloaded to allow assignment to your class. If you do not provide an overloaded assignment operator, the compiler will create one for you. Overloaded assignment operators should always include a self-assignment check.

New programmers often mix up when the assignment operator vs copy constructor are used, but it’s fairly straightforward:

  • If a new object has to be created before the copying can occur, the copy constructor is used (note: this includes passing or returning objects by value).
  • If a new object does not have to be created before the copying can occur, the assignment operator is used.

By default, the copy constructor and assignment operators provided by the compiler do a memberwise initialization or assignment, which is a shallow copy. If your class dynamically allocates memory, this will likely lead to problems, as multiple objects will end up pointing to the same allocated memory. In this case, you’ll need to explicitly define these in order to do a deep copy. Even better, avoid doing your own memory management if you can and use classes from the standard library.

Quiz Time

1) Assuming Point is a class and point is an instance of that class, should you use a normal/friend or member function overload for the following operators?

1a) point + point
1b) -point
1c) std::cout << point
1d) point = 5;

Show Solution

2) Write a class named Average that will keep track of the average of all integers passed to it. Use two members: The first one should be type int32_t, and used to keep track of the sum of all the numbers you’ve seen so far. The second should be of type int8_t, and used to keep track of how many numbers you’ve seen so far. You can divide them to find your average.

2a) Write all of the functions necessary for the following program to run:

int main()
{
	Average avg;
	
	avg += 4;
	std::cout << avg << '\n'; // 4 / 1 = 4
	
	avg += 8;
	std::cout << avg << '\n'; // (4 + 8) / 2 = 6

	avg += 24;
	std::cout << avg << '\n'; // (4 + 8 + 24) / 3 = 12

	avg += -10;
	std::cout << avg << '\n'; // (4 + 8 + 24 - 10) / 4 = 6.5

	(avg += 6) += 10; // 2 calls chained together
	std::cout << avg << '\n'; // (4 + 8 + 24 - 10 + 6 + 10) / 6 = 7

	Average copy = avg;
	std::cout << copy << '\n';

	return 0;
}

and produce the result:

4
6
12
6.5
7
7

Hint: Remember that int8_t is usually typedef'd as a char, so std::cout treats it accordingly.

Show Solution

2b) Does this class need an explicit copy constructor or assignment operator?

Show Solution

3) Write your own integer array class named IntArray from scratch (do not use std::array or std::vector). Users should pass in the size of the array when it is created, and the array should be dynamically allocated. Use assert statements to guard against bad data. Create any constructors or overloaded operators needed to make the following program operate correctly:

This programs should print:

5 8 2 3 6
5 8 2 3 6

Show Solution

4) Extra credit: This one is a little more tricky. A floating point number is a number with a decimal where the number of digits after the decimal can be variable. A fixed point number is a number with a fractional component where the number of digits in the fractional portion is fixed.

In this quiz, we're going to write a class to implement a fixed point number with two fractional digits (e.g. 12.34, 3.00, or 1278.99). Assume that the range of the class should be -32768.99 to 32767.99, that the fractional component should hold any two digits, that we don't want precision errors, and that we want to conserve space.

4a) What type of member variable(s) do you think we should use to implement our fixed point number with 2 digits after the decimal? (Make sure you read the answer before proceeding with the next questions)

Show Solution

4b) Write a class named FixedPoint2 that implements the recommended solution from the previous question. If either (or both) of the non-fractional and fractional part of the number are negative, the number should be treated as negative. Provide the overloaded operators and constructors required for the following program to run:

This program should produce the result:

34.56
-2.08
-2.08
-2.08
-0.05
-0.05

Hint: Although it may initially seem like more work initially, it's helpful to store both the non-fractional and fractional parts of the number with the same sign (e.g. both positive if the number is positive, and both negative if the number is negative). This makes doing math much easier later.
Hint: To output your number, first cast it to a double.

Show Solution

4c) Now add a constructor that takes a double. You can round a number (on the left of the decimal) by using the round() function (included in header cmath).

Hint: You can get the non-fractional part of a double by static casting the double to an integer
Hint: To get the fractional part of a double, you'll first need to zero-out the non-fractional part. Use the integer value to do this.
Hint: You can move a digit from the right of the decimal to the left of the decimal by multiplying by 10. You can move it two digits by multiplying by 100.

The follow program should run:

This program should produce the result

0.01
-0.01
5.01
-5.01

Show Solution

4d) Overload operator==, operator >>, operator- (unary), and operator+ (binary).

The following program should run:

And produce the output:

true
true
true
true
true
true
true
true
-0.48
0.48
Enter a number: 5.678
You entered: 5.68

Hint: Add your two FixedPoint2 together by leveraging the double cast, adding the results, and converting back to a FixedPoint2.
Hint: For operator>>, use your double constructor to create an anonymous object of type FixedPoint2, and assign it to your FixedPoint2 function parameter

Show Solution

10.1 -- Object relationships
Index
9.15 -- Shallow vs. deep copying

97 comments to 9.x — Chapter 9 comprehensive quiz

  • CrazyL

    add-on to last post:

    Better change the extra test case to 500.01: While the max value 32767.99 was converted correctly (I checked in the watch window while debugging), the output was "32768". I guess the precision std::cout uses to print doubles causes the problem…

    Anyway, 500.01 doesn’t have as many significant digits and works perfectly. Sorry for the misleading comment earlier!

  • CrazyL

    @Alex,

    yet another great quiz with quite a few caveats (like memory leakage or integer overflow in q4), it’s great that they’re getting harder 🙂

    I’d like to add another test case for quiz 4c) to check the extreme values (CTR+F: [*]). Why? Because they can lead into trouble if you implement the new constructor in a different way:

    In quiz 4c) I tried another solution for the constructor which seemed to work on the four test cases:

    While it seems to work on the test-cases, it didn’t work for the min/max values:

    While debugging, I found the culprit:

    For any number (|d| > 327.67) the type-cast overflows the range of int16_t (and always results in tmp = (-32768)_10 for me).

    Thanks again for the great tasks you have come up with: I believe in learning by error and that’s why they’re exactly right!

  • Luke

    Hi,
    From what I tested it seems that overloading the output operator in exercise 4 isn’t necessary at all. When it is removed the code still works because (I think) it automatically type casts from FixedPoint2 to double when needed. I guess you could comment it out from the code if you want :).

  • AndresT

    I think the solution in 4b will fail for some cases.

    For FixedPoint2 d(-2, 8),
    It would print -1.98 instead of the expected -2.08.

    For FixedPoint2 c(-2, -8),
    It would print -2.08 instead of the expected 2.08.

    • Alex

      Great catch. That’s what can happen when you don’t adequately test all of the different categories of numbers (positive, negative, zero).

      I’ve updated the code to accommodate this case.

  • A Potato

    Hi Alex,

    In question 3, I’m having trouble wrapping my head around the member initialisation. It looks to me like the m_length member has been given a default value of 0 but I can’t see any situation in which that would happen (and if it did, it would produce an error).

    To be specific, declaring an IntArray without parameters produces a compile error as in the following shortened version of the code:

    Alternatively, making the constructor a default one by using the following line to declare it compiles properly but errors out at the assert:

    Similarly, creating a separate default constructor allows length to be 0 but is kind of pointless.

    Regardless, my understanding is that in the provided solution, there is no way for m_length to be set to 0 despite the non-static member initialisation in this line:

    Am I making an incorrect assumption somewhere or is this a case where it doesn’t really matter?

    Thank you for these tutorials by the way 😀

    • Alex

      You’re correct. A few thoughts here:
      1) You shouldn’t be able to create an IntArray with length 0. What would that even mean?
      2) Initializing m_length to 0 is just a defensive programming technique -- in this program, it never gets used, because both constructors explicitly set m_length. But in the future, if we ever added a new constructor and forgot to set m_length, at least this way it gets a predictable value instead of garbage.

  • Cunni

    Hi Alex,

    In the final question (4d), you made the ‘+’ overload a friend function. Would it be better to make it a normal function since it doesn’t modify any members? Is there any difference?

    • Alex

      If it’s a friend function, it has access to the internals of the class it is adding. Most of the time that will be useful. In this case, it’s not strictly necessary, so we could have made it a normal function. In the strictest sense, a normal function would be better -- but in this case, because it’s such a simple class and we don’t need to force access of the internal data through specific functions, it’s not really worth sweating.

  • C++ Learner

    Hi, can you explain what this constructor does(I mean where it is used). When we create an object in main function we only use constructor with one parameter.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter