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

160 comments to 9.x — Chapter 9 comprehensive quiz

  • MoistyMire

    Why do we need a copy constructor in question 3?

    • Hi MoistyMire!

      The default-generated copy constructor will only shallow-copy the elements. It wouldn't copy the array elements, but only the array pointer. This would cause a double delete once both IntArrays get destructed.

      • MoistyMire

        But for which line do we need the copy constructor?

  • Hao

    Just curious, I didn't implement the "-" operator overloading, but the -a still works! How did it happen underneath the hood? Couldn't find a related explanation online. Thanks in advance.

  • Somebody62

    My solution for 4a-4c is this:

    When I went to complete 4d, all I had to do was add an operator>> and switch the main function for 4c with the one for 4d! It ran just as if I had added the overloaded +, -, and the == operator! This was caused by the double cast doing all the work!
    This is an interesting shortcut, maybe you should mention something about it, or maybe you could tell me what is wrong with relying on the double cast.
    Here is my operator>>:

    Thank you!

    PS: These code blocks are not working, maybe you can help me with that too. They only started malfunctioning when I edited my post.

    • Hi there!

      * Use <c*> headers rather than their <*.h> counterparts
      * Once you've done that, you should also use @std::int8_t, @std::int16_t, @std::abd and @std::round
      * Line 17: Use uniform initialization
      * Line 17, 23: Use double numbers when calculating with doubles (100.0 instead of 100)
      * @Operator<<: @fp2 should be a const reference
      * I don't like the rounding, but I can't think of anything better
      * @operator>>, line 2: Initialize your variables

      > maybe you could tell me what is wrong with relying on the double cast
      Your program needs to cast a lot, which is slower than actually doing the comparisons.

      > These code blocks are not working
      It's a client-side issue, refreshing the page fixes the highlighting.

      • Somebody62

        1. I was using a *.h header because I didn't know a c* header existed in this case.
        2. I did not know that std::int8_t and the like existed, and I thought that int8_t was the right one to use. But what does std::abd do? Do you mean std::abs?

        3. I forgot about that (I come from a Java background).
        4. Right.
        5. I forgot to (const) reference it.
        6. I think Alex said to use rounding.
        7. Isn't the variable going to get initialized anyway by std::cin? I've seen Alex do it this way before.
        8. Alex says to "Add your two FixedPoint2 together by leveraging the double cast, adding the results, and converting back to a FixedPoint2" for the addition of two FixedPoint2s. Maybe the unary operators should not rely on the cast, but Alex does say to use it for the binary addition. I find it interesting that this operator appears to be superfluous in the code, since it is automatically being implemented in the same manner by the compiler.
        9. I didn't think of refreshing it, thanks for the tip.

        Thank you for all your help and time!

        • 2. @std::int8_t is int8_t, but <c*> headers declare their contents in the @std namespace. Depending on the implementation, they might also be declared in the global namespace, you shouldn't rely on that though. Yes, I mean @std::abs.
          7. Initialized? No, that can only happen once and you missed the chance. Since C++11 the variable will be set to 0 if extraction fails. But if you can increase downward compatibility by something as simple as initializing a variable you should do so.

  • Aakash

    Question number 2a) does not follow the format of crayon syntax highlighter

  • Donlod

    Just a question about best practices:
    When copying the array data the solution here uses code like

    other is passed as a const reference.

    Since we already have an overloaded operator[] why do we not use other[i] and make the param non-const(or add an additional const operator[]() const) instead of accessing the member variable? Is accessing the member (significant) faster because we do not call the overlaoded function?

    • nascardriver

      Hi Donlod!

      Every function call adds overhead, including operator functions.
      Usually the compiler will inline small functions. If you don't trust the optimizer you can mark the operator[] as inline.
      If you have functions to access members there's rarely a good reason to directly access members. A good reason to directly access members is for example, when you want to create an identical copy of an object. You don't want to call any functions, because they might not return the value that's actually stored.

  • DecSco

    I tried the following code in my init() function for switching the signs to negative numbers:

    However, this produced wrong results for floats and I don't quite understand why. They are correctly converted into integers first:

  • David

    This constructor for 4c seems a tad simpler:

    • Alex

      That's a cool solve with some nice symmetry. Is it simpler? I'm not sure. It looks simpler at first glance, but:
      1) You multiply by 100 only to divide it back out for m_base. This seems unnecessary and is actually less clear than doing a straight conversion.
      2) You didn't use any casts, which makes your code simpler but will produce compiler warnings.

      Here's a slightly modified version of your solution with both of the above taken into account:

  • Olivier

    The quiz part 4d is leaving me perplex. The testAddition() function prints true 8 times like expected. But it does so without me having to overload operator+ (binary) and operator==.

    Moreover, the line below prints -0.48 like expected. But I don't even have to overload operator- (unary).

    I don't understand why I don't get any compile error. I'm using Microsoft Visual Studio Community 2017 Version 15.6.5.

    • nascardriver

      Hi Olivier!

      If you don't supply @operator-, @operator double() will be ran before the - is applied to the double.
      Same with @operator==, your FixedPoint2 will be converted to a double before the comparison is performed.

    • J Gahr

      I have the same problem with the testAddition() function. Does it have something to do with the use of anonymous objects?

      Regarding this line:

      ...w/o the @operator-, it prints 0.48 for me, but with @operator- it prints -0.48, so that works as nascardriver says.

      Still haven't figured out the testAddition() function's issue yet.

  • Ran

    Hi nascardiver:

    I just want to share a little bit experience I have.

    When I try to solve the final problem of this section. I solved the
    problem quite quickly until I to write the overload operator>>.

    I spend a whole morning to solve this problem but fail. I try so many
    times without making the program running.

    Then, I give up for the last problem.

    After that, I try to read Alex code, which is not exactly the same as
    mine. But, the most astonishing part of the problem that cost me a
    whole morning is that the overloading function of operator<< is not
    hard as I thought. I probably had solved the problem in a different
    format.

    So, I just imitate Alex's code to run the program. However, the
    program could not run. At that time, I thought that it might be
    related to some functions of my code, I checked all of them, modified
    them, but failed to run program. I was very upset at that time.

    Then, I copy Alex's code entirely on my machine (OS:win7-64bit with a
    **default** MS2017). You know what? The code just not run!

    What?!

    So, I suddenly realized that the only difference is the "trick"
    (making console not automatically exit).

    I used the following sentence to do this when I program every problem:

    This is where the trouble comes from.

    When I overload I/O, I must trigger something that I do not know. (I
    would be appreciate someone could explain).

    If you use

    You gonna have no trouble.

    • nascardriver

      Hi Ran!

      Your program runs, but it runs so fast that you don't see it. One way to prevent this is keeping the console open by using std::cin like you did.

      should not be considered a proper solution, it will only work on Windows. Don't use @system unless you have to.
      Other ways to solve this without modifying the program are
      * manually running it in a console (Open path to your binary -> Shift+Right click empty space -> Open in Console (or similar)).
      * placing a break point in the end of @main
      Most IDEs offer an integrated terminal so you can see your program running right in the IDE, I don't know why MS hasn't implemented this in Visual Studio yet.

  • Ran

    Hi Alex:

    I found your solution to 4b is way much better than mine in the flowing
    points:

    0. Your code puts the logic to determine the sign of the date in the
       constructor, whereas I put them in the friend function. In my case,
       it only solve the problem once, but I can feel the translation of
       my code. Put the function within the right part of the code. I
       think this is a good example to show my weakness.
    1. Your code output a double variable to ostream, whereas I just
       output two integers to ostream. This is what I miss when I try to
       write the program. In order to do this, as you can see in my code,
       I have a hard time to deal with the decimal part. I just hard code
       this part by adding a logical selection (point.m_fraction < 10),
       which made the code very bad. As you can see, in the logical
       selection there's only a tiny difference (<<".0"<< and <<"."<<),
       but the code is essentially the same.

    My solution:

    • Alex

      If it makes you feel better, I went through a _lot_ of incorrect/poorer versions of this program before I landed on the current solution.

  • Ran

    Hi everyone:

    This is my code to question 9.x.3, due to lacking of experience, I forget to add a lot
    of things in the following code, but the following code could run on
    my computer without any problem. I also go through your code. I have
    the following questions to ask:

    0. I forget to write a destructor, but I did not see anything
    wrong. Is the destructor code in your code(Line31-Line34) a necessary?

    1. I did forget to do a deep copy in my copy constructor, so c++
       automatically created a public copy constructor for me. C++ knows
       nothing with my class, so it just did a member-wise copy. This is
       called a shallow copy. As Alex described in section9.15: "when
       designing classes that handle dynamically allocated memory,
       member-wise copy(shallow copy) can get us in a lot of trouble."
       However, I did not see anything wrong in my class with a
       dynamically allocated memory(*m_array). Why my class works fine?
      
    2. I forgot to write an assignment overload(operator =), but in my
       code, nothing wrong happen. why?

    • nascardriver

      Hi Ran!

      Question 0:
      It's not necessary, but I highly suggest you implementing it, because you're allocating memory without ever deleting it and thus causing a memory leak.
      Question 1 and 2 are answered in the code. This code still doesn't do what it's supposed to do, I suggest you giving it another try better understand this chapter.

      • Ran

        Thanks nascardiver! I will redo this problem sometime. I am really
        struggle with a lots of concepts within this chapter. Following is the
        comments and questions after I read your suggestion. I am really
        appreciate it if someone could explain these questions.

        • nascardriver

          > But I don't know the reason why this is a better choice.
          Uniform initialization will cause error when using wrong types in some cases where direct/copy initialization would implicitly cast and thus might cause undesired behavior.

          > why this is a unused variable?
          Sorry, I wrote this before seeing that you have another variable named 'm_array' and was confusing them. It's not unused but you're never assigning a proper value to it.

          > Is this a potential improvement for this question?
          I guess so, in lesson 2.1 Alex wrote
          "Rule: If you’re using a C++11 compatible compiler, favor uniform initialization"
          He's using direct/copy initialization in the most lessons. I suspect he wrote them before uniform initialization was introduced.

          > InArray(int size = 0): m_size{size}, *m_array {nullptr} {}
          This doesn't work, for three reasons:
          - You misspelled IntArray
          - You're dreferencing @m_array
          - You're initializing @m_array with a nullptr
          This should work:

          > My computer works fine without {0}, what is the function of {0}?
          It sets the first element of the array to 0. There's no point in doing it, because the array will be zero-initialized when using empty curly brackets.

          > why you use this-> ?
          > Is your code equivalent to m_array = new int[m_length]?
          It is the same. Using the this pointer makes it easier to understand which variables/functions you're accessing and avoids naming conflicts.

          > Why VS2017 does not give me this kind of warning?
          It should show you a list of warnings alongside with errors in a table at the bottom by default. If you're not getting warnings try changing the Warning Level (Settings -> Configuration Properties -> C/C++ -> General -> Warning Level) to the highest level. If the warning still doesn't show up I can't help you, I'm not using Visual Studio.

          > Why we need to delete[] m_array?
          If you don't delete the array you're leaking memory.

          > Can someone explain what why the overload was working?
          The default operator= creates a shallow copy. So instead of allocating memory for a new array it will just copy over the pointer.

  • Ran

    0. I got some trouble to initialize my private data.

    This is part of code:

    This is what I thought the program should work:

    00. When an instance of the class IntArray is created using this
        sentence in the main program:

    It call the constructor I just created. At this time m_size is equal
    to 2. I thought *m_array will be initialized as it was described in
    the assignment of the constructor:

    However, it just not work as I expected!

    When I overload the subscript operator like this using the class
    member function:

    After I execute the main program, it always through the following
    error:

    Exception thrown: write access violation.
    a.m_array was nullptr.

    So, how I solve this issue? It is simple but in a really ugly way:

    I defined the class private variable like this:

    And I works! But I am not comfortable with defining the variable like this, but I do not have a better solution right now.

    • nascardriver

      Hi Ran!

      I partially covered this question in your other comment, here's an example:

      Output
      0 0 0 0 0 0 0 0

  • Ran

    I found a more creepy thing after I read Alex code:

    This is my original definition of the operatro+=:

    I treat the right hand side of the equation as an object of the class
    Averaged.

    To make the code running, I have to write a constructor like this:

    If I do the solution(using the default constructor) given by Alex, it
    won't compile.

    My code, actually treat the number on the left hand side of that
    equation as the first private member m_sum using the initialization
    list. But you can see that, to make the program running, I did an
    awful thing:

    But I still do not know why "this" pointers still works in the
    following code?

    How this two "this" work good?

  • Ran

    This is my version of question #2. I have to admit that the following
    solution is not completely written by myself. I actually try very hard
    to solve this question.

    At the very beginning of solving this problem, I try to overload the
    operator '+'(binary operator using friend function just as we did in
    section 9.4) and overload operator '=' (in section 9.14). I thought
    that the complier should know how to deal with += automatically, but I was
    wrong.

    And I did not try to overload +=, instead I google the term: "how to
    overload +=".

    And I found one solution by Prasoon:

    https://stackoverflow.com/questions/4581961/c-how-to-overload-operator

    He used:

    Although I do not understand exactly what's going on here,
    particularly the pointer this , I "copy"
    his code into mine, and it worked.

    I go back to the section 8.8, re-read the hidden "this" pointer.

    "By having functions that would otherwise return void return *this
    instead, you can make those functions chainable."

    I also go back to section 6.12 to figure out the exactly meaning of
    ->. And it turns out that this is a second form of member selection
    for a pointer.

    • nascardriver

      Hi Ran!

      Good job solving the quiz!

      >

      Initialize your variables

      >

      You went a bit too for here, have a look at Alex' solution

      PS: Closing CODE tags use a forward slash (/)

  • Ran

    Am I understand correctly:

    The key difference between normal function, a friend function, or a
    member function is that the normal function can not access to private
    class member variable.

    What I observed from Alex suggestions of choosing what kinds of
    functions to use to overload something is based on "modification".

    If the overloading does not change its operand, then we use a normal
    function, otherwise we use a member function or a friend function.

    Why not just use one thing? For example, we use member function for
    all cases? What kind of cost we need to compensate of using one type
    of function?

    • Ran

      I go back to the section 9.2a and I find the following suggestions:

      "In general, a normal function should be preferred over a friend
      function if it’s possible to do so with the existing member functions
      available (the less functions touching your classes’s internals, the
      better)."

      It seems that the less functions written within the class the
      better. But, again, why? Performance?

      • Alex

        No, not performance. It's more about simplification. The more functions your class has (especially those that can modify the internal state of the class), the more complex it is to understand, and the harder it is to modify/update. Putting overloaded operators outside the class makes your class slightly simpler to understand, and easier to update, because there are less members that need to be considered when making an update.

    • Ran

      I update my understanding of choosing (normal/friend/member functions)
      when I need to write an overloading function.

      It based on the "modification".

      If the function need to change things within the class, you have no
      choice, go with member function.

      If the "modification" is not part of your new function, then check the
      accessibility of your function. If you can access from outside of the
      class, go with normal function. If you need to access the private
      variables, go with friend function.

      All in all, if you can put the function out of the class, that's the
      best choice because you can make your class slightly simpler to
      understand and easy to update.

  • Frieder

    Hi,
    you might want to add the following comment. It took me a quarter hour before my unclarity vanished...

    I was feeling a bit uneasy when thinking about positive m_base and negative m_decimals which actually should be calling for a subtraction. And the impossibility of -0 even after reading your hint about making both signs negative didnt help either. At least the comment shows that somebody went thru the math 🙂

    Frieder

  • Denis

    Alex,

    Thank you for writing this series, I am really enjoying working my through all of this thanks to your tutorials.

    I have two questions on Question 3 of this quiz:

    1) When I wrote my own program to try and satisfy the requirements I did not include a copy constructor and I still got perfectly good output. I understand when we dynamically allocate memory it is unwise to use the default copy constructor, but when I was working through the given int main() I thought I took care of that with the overloaded = operator. Why is it necessary to use a non-default copy constructor int his problem?

    2) I also did not include a return *this; in my overloaded = operator, how did my program still work as desired/why is the return *this important?

    Thank you!

    • nascardriver

      Hi Denis!

      1)
      You think it worked, but only until you modify one of the IntArrays. Let's look at an example.

      Output with custom copy constructor
      1 8 2 3 6
      5 2 2 3 6
      5 8 3 3 6
      5 8 2 4 6
      5 8 2 3 5

      Output with default copy constructor
      1 2 3 4 6
      1 2 3 4 6
      1 2 3 4 6
      1 2 3 4 6
      5 8 2 3 5

      2)
      C++ supports this sort of assignment

      If you don't return *this in operator= then a=b will return nothing, you try to assign @c to a void and you get an error.

      • Denis

        Thank you! Also after stepping into my program I realized that the default copy constructor is called when the program "returns a" the fillArray() function which explains why it is important for the quiz question as well.

  • Karl

    Hey Alex, thanks for this great tutorial. I didn't find any better one. =)
    I have two questions to quiz 3, the operator= overloading-function:

    Isn't it more efficient to only delete the array, if the new array has another length?

    Also, I didn't use *this and &amp;array to compare the two inputs, but instead used this.m_array and &amp;array.m_array. It works but I am not sure if used the right way?

    • Alex

      1) I'm not sure what you mean by "only delete the array". As opposed to what?
      2) I think that works, since m_array should be specific to each IntArray.

      • Karl

        Thanks for the fast answer. To clarify my question:

        I am deleting the array, if they have not the same length. If they have the same length, I don't delete it. In your code the array gets deleted no matter if they have the same length or not.

        And I was currious, if it's better to delete it in any case for safety (?), or to skip that step to save calculations.

        • Alex

          I see. Yes, if the new array is the same size as the old one, there's no reason to delete the old one and reallocate a new array of the same size. You could just overwrite the values in the old array.

  • John Halfyard

    Why do we need to use 'friend' here when the function is defined inside the class?  (for Question 3)

    • Alex

      Because if this weren't a friend overload, it would need to be a member overload, and since the leftmost parameter is std::ostream, we'd have to add the function to std::ostream. Since std::ostream is a member of the standard library, we can't modify it. So we are forced to use the friend overload method.

  • John Halfyard

    Just a clarification on question 1, if the insertion operator "<<" is a friend of class Average, should we not put its definition outside of the class and not inside it as written in the solution?

  • DOG_TRAINER

    Question 3: Do you think my is on par with yours?

  • Luhan

    Shouldn’t you check if the pointer which you is trying to copy isn't a dangling pointer? like this:

  • What TheHex

    Hello Alex. I made version where += is a friend function.

    Average& operator+= (Average &avg, int x){

        avg.sum += x;
        avg.ints += 1;

        return avg;

    }

    the return avg; is only required so you can chain. Now What I don't get however is when I used "Average operator+= (Average &avg, int x)" instead of "Average& operator+= (Average &avg, int x)" It would work fine except the chain wouldn't compile. And I really don't understand why. (I removed the & from the function return type when declaring the function)

    • Alex

      The avg parameter is a non-const reference parameter, which can only bind to l-values. When your function is returning an Average by value, the returned value is an r-value (as it's a temporary value), and thus can't be chained into another call to +=. By returning a reference like you should, the return value stays an l-value, which can then be chained.

  • 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 all code inside code tags: [code]your code here[/code]