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:

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

238 comments to 9.x — Chapter 9 comprehensive quiz

  • cxlf

    Hi, thanks for the tutorial.

    When following this tutorial I sometimes have long breaks which makes me forget how to do some things properly.
    I think having the third example be something like this:

    would help a lot.
    If you haven't made the deep copy constructor the last cout would output random values. That would help anyone like me realize that they've done something wrong instead of noticing it when reading the solution.

    Anyway, that's just a small nitpick. These tutorials have helped me a lot.

  • Neon

    For 3), wouldn't it be a good idea to use std::size_t as type for length, because it automatically makes sure the user can use the maximal size possible and not more.

    • Hi!

      Your argument is totally correct. The issue with `std::size_t` is that it's an unsigned type. Unsigned types can easily lead to unintended implicit conversions which might cause unexpected (but well defined) behavior.
      It's easier to use a signed type, even if we can't use the full range.

      > and not more
      Don't use types as limits. You'll find yourself overflowing the type. Manually make sure you're not exceeding the bounds instead.

  • Atas

    Shouldn't we make the unary operator- const? I initially did so but then noticed your solution doesn't. I tested it and it compiles fine even if you do

    So why can't you use a non-const operator[], but you can use non-const unary minus on a const object?

  • Tom

    Quiz 3) Why can't I use for-each loop?

    The error message is something like "begin was not declared in this scope".

    • X must either be a fixed array, have `begin` and `end` member functions, or there must be a `begin` and `end` function that take an argument of the same type as `X`.
      None of these conditions is fulfilled for `int` (`array.m_length` is an `int`).

  • Dany

    Interesting, it works just fine with only typecast overloading(except for ">>").

    • Because now, whenever you apply an operator to a `FixedPoint`, that instance is converted to `double`, the operator is applied, and the result is converted back to `FixedPoint`. That's slow and might lead to loss of precision.

  • Vir1oN

    It`s me again, could you explain to me why the following overload of typecast operator doesn`t work?

    Is your solution the only one correct? It just seems less intuitive to me

    • The entire calculation is performed using integers, you're only casting the result to `double`, a lot of information has been lost up to that point.

  • Vir1oN

    Hi

    I`d be grateful if you could point out why the following produces two linker errors
    1) Error LNK2019    unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Average &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAVAverage@@@Z) referenced in function _main ;    
    2) Error LNK1120    1 unresolved externals    .

    The only major difference from your solution is that I used separate function to calculate the average, and called that function in the overloaded "<<" operator.

  • Dimbo1911

    Same for this one

    • - Initialize your variables with brace initializers.
      - Line 15: That 0 doesn't do anything. Empty curly braces set all elements to 0.
      - Line 25-28 and 64-65 can be replaced with `std::copy(source.m_array, source.m_array + source.m_size, m_array)`. This isn't covered on learncpp.
      - Line 43: Single quotation marks.
      - Line 22 is always true.
      - Line 59: You don't need to delete the old array if it has the same size as the new one.

  • Dimbo1911

    Good morning, how does this code seem? Is it better to use int32 and 8_t like this or with std::int_least32_t and 8_t respectively and why? Thank you for your time :)

    • least and fast, those always exist. The `std::int*_t` types only exist if the implementation/platform supports them. @Alex, this lesson needs an update.
      Line 26 should use '/' in single quotation marks, other than that you code is good.

  • Ben K

    I seem to be having problems with the lines that have no decimal overflow! Oddly, when I print out the addition by itself, it looks exactly like the sum by itself. Here are my codes, mostly taken from the solution but somewhere I modified something and it's throwing errors.

    From source.h:

    From main.cpp:

    It gives the following in the terminal:

    • The parameters of `operator+` and `operator==` are non-const.
      Adding 2 `FixedPoint`s yields a temporary, which cannot be used as a non-const argument.
      This means that all your additions and comparisons are performed by casting to a double, and applying the operation to that double.

      • Ben K

        Thanks for the help! I don't understand yet why adding two FixedPoint objects yields a temporary object, or why this cannot be used as a non-const argument. Further, how does this effect only happen on sums where there is no decimal overflow?

        • > I don't understand yet why adding two FixedPoint objects yields a temporary object, or why this cannot be used as a non-const argument.

          > how does this effect only happen on sums where there is no decimal overflow?
          It's caused by floating point inaccuracy. Compiling your code in 64 bit on my machine produces the expected output, 32 bit causes your output. All your additions behave the same, but for some, the calculation is accurate.

  • Timo

    Hi Alex!
    (At least) Clang 8 doesn't compile the code for quiz 3:

    I think this line of code is there by fault.

    Cheers
    Timo

    • It's there on purpose to make sure you're implementing a self-assignment guard.
      Assigning a variable to itself is bad, so clang throws a warning, and it's treating warnings as errors, so you get an error.
      Since Alex recommends self-assignment guards and strict warnings, this lesson needs to be updated to be compatible with those recommendations.
      Until then, you can comment out the line that's causing you trouble.

      • Alex

        I didn't know there were any compilers throwing warnings on self-assignment. I've updated the lesson to avoid the direct self-assignment.

  • Jens

    Hello,

    in your solution for quiz 4 in the constructor, line 19 and following:

    Why do you compare against 0.0 and not just 0 as m_base and m_decimal are both integer types?

  • Ben

    I couldn't get problem 3 to work, even when I copied everything exactly. When I ran it, I would get:

    However, I commented out the operator= function (lines 67-93 in the solution) it suddenly started working! I got:

    as expected. What gives?

    • Your offset line numbers lead me to think that you didn't copy the solution.

      • Ben

        I mixed the digits up. 69-73.

        • That's still just a part of a function, without those lines your code shouldn't even compile.
          Is this the code you're talking about?

          • Ben

            Oh you're right, I didn't copy it completely. I tried to paraphrase it really. Here is what I have:

            In source.h:

            In main.cpp(not including all the #include lines):

            When I do this, I get:

            but if I comment out my definition for operator=, I get

            as expected.

  • Shri

    I had a question. Thanks in advance for taking the time to reply! :)

    I do not understand why overloading unary operator like - requires a return type
    whereas overloading typecasts does not require return type.

    I understand these are the rules, but what is the logic for the rules being formulated this way.

    One guess I have is that while returning a unary operator we can potentially return by reference or return by value, since there are two options explicitly mentioning return type was mandated for unary operators.

    Whereas in the case of overloading typecasts, one can only return by value, therefore the compiler implicitly understands what the return type is.

    Is my guess correct?

    • Yes.
      Also, the unary operators can be overloaded without causing confusion.
      eg. we could overload them to to promote a MyFloat to double or demote it to int.

      (That's not so great of an example, because the unary - is usually the negation of a number.)

      But you'd always expect this

      @db to be a double. Allowing @MyFloat to return a different type from a type cast is greatly confusing.

  • Shri

    Hi, In the answer to 4b), I did not understand the following piece of code:

    I did not understand why we are sure that m_decimal will be a 2 digit number? int8_t has a range of -128 to 127.

    Please help me understand.

    Thanks in advance for taking the time to answer!

    • Alex

      In the code, it says:

      So as of now, there's nothing preventing the user from passing in a decimal outside the intended range. The class should guard against this, but currently doesn't for brevity.

  • Anthony

    Hi Alex,

    Sorry to trouble you again, but I feel I really need to understand this. Here are some alternate versions for a couple of the operators:

    1) Is there any reason why this is any worse or better than the version you have with the two formal parameters rather than one?

    2) Again, I have tried to make a version with just one formal parameter (and using the implicit 'this' pointer). I notice that one can't use 'const FixedPoint2 &fp' for the function parameter. Why is this? Is the problem the cast? But why should this be? When we use the cast we're not altering fp, are we? I.e. when control passes back to the caller, fp won't have been changed..

    Not sure why, but I seem to have mental blockage with consts in general.

    Thanks for all your help!

    • 1) See lesson 9.4 "When to use a normal, friend, or member function overload".

      2) You need to mark @operator double() as const

      Also note that a brace initializer can be used here

      Actually, you could also remove the curly brackets, but I don't like that.

  • missingsemicolon

    Quiz 3)
    Is the following code also ok?

  • Jack

    Q 4d.
    Probably due to the way I approached this (I started with the given main() and fixed things that the compiler didn't like 'till it all worked) I found that the overloaded '++' '-' '+' were not needed. I assume there were default public assignment operators doing the job.
    My question, is this a good result? should we use these or write our own 'just in case'?

    • Hi Jack!

      * Line 25, 95, 74-88: Initialize your variables with brace initializers. You used direct initialization.
      * Line 39, 49, 58: Initialize your variables with brace initializers.
      * Line 39-42: Initialize rather than assign
      * Line 27: Missing assert
      * Don't use @std::int*_t, they're optional type (ie. they might not be supported by all compilers). Use @std::int_fast*_t or @std::int_least*_t.
      * Use double literals when calculating with doubles (2.0 instead of 2 etc.)
      * Line 67: Magic numbers

      If you remove operators and it still works, it means that there's implicit conversions going on which seems to do the same.
      Those are not only slower than custom operators (in most cases), but can also cause undesired behavior.
      If you use an operator, overload it (Unless you know that the default generated one does exactly the same).

  • Satti

    Hello, I have a small question that I can't seem to answer.
    For question number 1, I have written everything identical to the solution code except the following:

    When I compiled and ran the above code, I got 6.4 at the end, which means that it calculated only (ans += 6)/5 and not the other +=10 also.
    I then noticed that in the solution, there was a '&' after Average and I didn't. So I added the '&' to my original code, and it worked perfectly.
    I don't understand why this would work and mine wouldn't. Why does it make a difference?

    • avg += 6 executes first. It increases @avg.m_total and @avg.m_numbers. It then returns a copy(!) of @avg.
      copy += 10 executes next, this doesn't affect @avg.

      Lesson 6.11 and 7.4a

  • AlexR

    Hi. In quiz 3 I was playing around with the code in order to understand the copy constructor a bit more but there's something I just can't understand:

    Consider this code (I'm pasting the relevant code only).

    Note that in this code I removed the reference to the second parameter of operator<<().

    The copy constructor should be called every time I want to print with cout because the object will be passed by value (I know this is discouraged and a reference should be used). If I remove the copy constructor the compiler will use the default one which does a shallow copy and that means I should expect a bad output in the console.
    However in my machine I'm receiving this:

    Why is only the second one incorrect and not the first one if in both calls to cout the default copy constructor is doing shallow copies of the object?

    • Hi Alex!

      Line 29: You don't have a copy constructor, this line alone will crash your program. It doesn't, because of compiler optimizations.
      Assuming your compiler optimizes line 29 and your program continues with @a in a valid state.
      Line 30: A shallow copy of @a is created for @operator<<. The clone's @m_numbers pointer is valid, there's no problem.
      @operator<< finishes, calling the clone's destructor. The destructor deletes the array (!).

      Since the array was shared between @a and it's clone, @a's @m_numbers is no longer valid.

      Line 33 uses a reference parameter and doesn't access @a's @m_numbers. This is fine.
      Line 34 passes @a by reference, but also accesses @a's @m_numbers. Behavior is undefined.
      Line 36: @b's array is valid, but was initialized with undefined values (In your case, zeroes). When @operator<< is called, what happened to @a in line 30 will now happen to @b.

      At the end of @main, both @a's and @b's @m_numbers have already been deleted. Since they go out of scope now, their destructors will be called and another delete will be performed. Behavior is undefined (Or a crash is guaranteed, I'm not sure).

  • Dean Cutillar

    Hello all,

    I have been slowly but surely working through these chapters learning C++.  I have a lingering confusion over distinguishing a pointer vs a reference to a variable.

    Here is an example:

    So this function to overload operator=, does it return an address to a fraction, or a reference to a fraction?

    by returning *this at the end of the function, is that a give away that means it's an address?  Also, I thought "*this" was a de-reference and would result in the actual data being pointed to.  So does it return an address, or the actual data pointed to?  

    Lastly, in the "self assignment guard" if statement (this==&fraction), so I always read "&" in the parameter as "a reference to" the variable.  But that statement is comparing it to "this" which is a pointer variable.  So is it a pointer?  Does including the "&" in the body of the function mean the data being pointed to?

    Please clarify these issues.  It really would help me a lot.  Thanks.

    Dean.

    • Alex

      This function returns a reference.

      If & is applied to an object, it means address of.
      If & is applied to a type, it means reference (in the case of function parameters, this case applies)

      Since this is a pointer, *this is the object being pointed to by "this". In the self-assignment guard, &fraction means "address-of" in this context, which is a pointer.

  • Silviu

    Hello, i was reading the code and something slipped and noticed just now.
    ->operator double()
    usually is operator()(),operator+ and so on ...

    There is a lesson that i missed or something ? can you explain ? Thank you.

  • Jörg

    Hi, wouldn't it be better to make the operator- overload return a const reference instead of a copy, like that:

    Is there a reason not to?

  • Danang

    this is my code for number 4a - 4c :  i use int for member variable to increase size of decimal number can hold to handle the case where decimal is > 99 or < -99 here.

    is this better way to handle the case where decimal is > 99 or < -99 than using int16_t or int8_t?

  • MikiBG

    In the last question, operators +, >> and << do not need to access member variables. Is there any reason why one should prefer to define them as friend functions over just normal functions?

    • Alex

      Most seasoned devs would tell you it's better to define overloaded operators as non-members if you can, because it "improves encapsulation" by ensuring you don't access internal members directly, and reduces the number of functions you have to consider updating when making a change to the class.

      I've updated the solution to define those outside the class, as per best practice.

  • kjm

    Hello,
    Why not to use a single int32_t field for 4a? (fixed-point behaves essentially like an integer while memory is aligned anyway)
    Thank you.

  • Hi, worked through 4(b) and then moved on to 4(c) (it's only another function after all) and ended up with the following:

    Hopefully this is good enough to move on to the next?

    • * Line 17
      * Line 19: Unnecessary check. You're checking again in the following lines
      *-Line 24, 36, 38: Can be written without repetition of variable names
      *-@FixedPoint2::FixedPoint2(double): Favor initialization over assignment
      * Line 46: @round should be @std::round
      * Line 46, 51: Use double numbers when calculating with doubles (100.0 instead of 100)
      * Line 64-76: Way to go

      > Hopefully this is good enough to move on to the next?
      See if you can work out the *- suggestions first. Other than that you code looks fine.

  • For number 3 I give you:

    • * Line 16, 22, 24, 36, 53, 65, 68
      * Line 19: That 0 only initializes the first element. Empty curly brackets mean "initialize the entire array to default values (0)".
      * Line 43: Compilation error. Should be

      or for large types

  • Well, here's my attempt at number 2.  It produces the correct result so I suppose it is ok

    • Hi Nigel!

      * Line 11, 12: Thank goodness!
      * Line 11, 12: Should be @std::int32_t and std::int8_t, but those shouldn't be used at all, because they're optional. Alex is to blame for this, you did what you were asked for.
      * @Average::Average is unused, you might as well remove it.
      * Line 37, 54

  • TheBendeee

    Hi! I have problems with understanding this part of the code:
    (line 31:)

    Why does this work, and how?

    • Alex

      Consider a number like 2.56. 2 would be stored in m_base, and 56 would be stored in m_decimal. To reconstruct the actual value, we first need to convert m_base from 56 back to 0.56. We do this by dividing it by 100. We static_cast(m_decimal) to make sure we're doing floating point division instead of integer division. Then we add m_base to finish reconstructing the entire number.

      • Silviu

        Hello,
        Alex great example and great lessons, but i think you should explain this in your code, it's still a key part in the code, that is not really explained and I think it's a good explanation and very helpful.
        Thank you.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]