CS 70

Before You Start…

  • Pig speaking

    I wanna learn MORE new stuff!!

  • LHS Cow speaking

    Sure, but before we do, let's make sure we can remember some of the old stuff.

Review: Using Constructors

  • RHS Cow speaking

    Let's remind ourselves about how we use constructors.

  • LHS Cow speaking

    But we'll also start thinking a bit about a topic we'll look at more deeply in this lesson: The idea of “temporary objects”.

Suppose we have a LabNotebook class that supports the idea of a default lab notebook, as well as copying and assigning lab notebooks. How would you create a new default LabNotebook called myNotes, allocated on the stack of the current function? Provide the necessary declaration.

Give a declaration for a new default LabNotebook, allocated on the stack, and accessed using the variable name myNotes. (Give a complete line of code, including the trailing semicolon.)

To create a default LabNotebook, we can either say

LabNotebook myNotes;

or

LabNotebook myNotes{};
  • Duck speaking

    How about:

    LabNotebook myNotes();
    
  • Bjarne speaking

    No—that would be a declaration of a function called myNotes that takes no arguments and returns a LabNotebook.

  • Hedgehog speaking

    !!!

  • LHS Cow speaking

    If we use curly braces for constructor arguments, we'll never be caught out by this C++ “irregular verb”.

  • RHS Cow speaking

    What are all the things that would happen if we'd written

    LabNotebook myNotes = LabNotebook{};
    

    instead?

What happens if we write LabNotebook myNotes = LabNotebook{}; ?

The code above is a call to the copy constructor, and it's pretty much the same as writing

LabNotebook myNotes{LabNotebook{}};

This version creates a redundant temporary (default) LabNotebook object. We haven't spoken much about temporary objects or drawn them on our memory diagrams, but they're a real thing, and to run this code C++ has to have space for not one but two LabNotebooks behind-the-scenes—it needs space for myNotes and also a temporary space to hold the object created by LabNotebook{}.

  • Pig speaking

    Can we make MORE temporary objects? Just for fun?

  • LHS Cow speaking

    Sure.

  • Hedgehog speaking

    Noooo…

This code would also initialize myNotes but with lots of redundant object creation and copying:

LabNotebook myNotes{LabNotebook{LabNotebook{LabNotebook{}}}};

In addition to the single LabNotebook that we're trying to create, how many extra (temporary) LabNotebook objects do we make here along the way? (just the number)

We make one temporary default lab notebook with LabNotebook{}, and then copy the value three times, twice into additional temporary LabNotebook objects, before finally copying the value from our final temporary object into our newly declared variable myNotes.

  • Bjarne speaking

    Actually, the answer on real C++ systems may be zero. The compiler is allowed (and sometimes required) to perform “copy elision” to avoid wasting time in redundantly copying data around.

  • LHS Cow speaking

    Thanks Bjarne, that's a cool optimization, but in this class we won't worry too much about remembering it.

  • RHS Cow speaking

    If you're asked to count the number of copies that happen, count all the ones you can see.

  • LHS Cow speaking

    But if you're drawing a memory diagram and want to take a shortcut and skip creating an object in one place only to immediately and unconditionally copy it somewhere else and throw away the original, it's okay to just draw it where you know it will end up—the shortcut is probably what would happen anyway.

  • RHS Cow speaking

    But no one will mind if you don't take shortcuts.

What would happen if we'd written the following instead?

LabNotebook myNotes = new LabNotebook{};

This code will…

  • Pig speaking

    Could I throw in MORE *s and write it like this?

    LabNotebook myNotes = *(new LabNotebook{});
    
  • Bjarne speaking

    Technically, it would compile…

  • LHS Cow speaking

    Yes, it would. But no, don't do that! Don't just scatter *s into your code to try to make it compile. That code would have a serious problem.

  • Hedgehog speaking

    Argh! I don't want to even think about this!

  • LHS Cow speaking

    Neither do we! Please, folks, never write anything like that.

  • RHS Cow speaking

    That code would cause a memory leak, because we'd create a temporary object on the heap and never be able to delete it.

  • LHS Cow speaking

    We'll come back to this piece of code a bit later when we have some more tools in our memory-diagram toolbox and we can trace through its awfulness in detail.

Review: Allocating Things on the Heap

If we wanted to make a new LabNotebook object on the heap, we could do so by running:

LabNotebook* myNotesPtr = new LabNotebook{};

If we allocate things on the heap, what should we be careful about? (select all that apply)

Review: Iterating Over An Array

Suppose that inside a LabNotebook, there is a data member that is an array of one hundred LogEntry objects, declared as

LogEntry entries_[100];

Imagine that we want to print out these log entries, and that there is a printToStream member function for the LogEntry class.

One option would be to write the code

for (size_t i = 0; i != 100; ++i) {
    entries_[i].printToStream(std::cout);
}

Another option would be to write

for (size_t i = 0; i != 100; ++i) {
    (entries_ + i)->printToStream(std::cout);
}
  • Goat speaking

    What's the deal with -> again?

  • LHS Cow speaking

    Saying ptr->memberOfSomeKind is a convenient shorthand for saying (*ptr).memberOfSomeKind.

  • RHS Cow speaking

    So the code above could be

    (*(entries_ + i)).printToStream(std::cout);
    

    instead, but that's much uglier.

  • Goat speaking

    Yes, that is ugly.

There is also another way of writing this loop where our loop variable is a pointer rather than an integer:

How would you write the code so the loop variable is a pointer rather than an integer?

Here's a fill-in-the-blank template to get you started, copy it into the box below and fill in the blanks:

for (____* p = ____; p != ____; ++p) {
    p->printToStream(std::cout);
}

Here's how to do it this way:

for (LogEntry* p = entries_; p != entries_+100; ++p) {
    p->printToStream(std::cout);
}

This start/past-the-end pointer style is widely used in C++ coding.

In Week 5 there was an optional exercise using this technique, which shows some of the built-in C++ functions that support working with an array using a start pointer and a past-the-end pointer.

Check out the example code on Online GDB:

  • Hedgehog speaking

    That was a lot of review!

  • LHS Cow speaking

    Yes, it was, but we're going to use some of these concepts as foundations we'll build on in this lesson.

  • RHS Cow speaking

    If you're unsure about any of what you've seen here, you can go back to the previous weeks' lessons to refamiliarize yourself.

(When logged in, completion status appears here.)