CS 70

Initializing Heap Memory in Constructors

  • LHS Cow speaking

    On the last page we decided that we should change our Barn class.

  • RHS Cow speaking

    Our new version can store any number of Cows in an array on the heap!

In particular, we decided to change residentCow_—which was a Cow—to residentCows_, which is a Cow* that will point to an array of Cow objects on the heap:

class Barn {
 public:
    Barn();
    Barn(size_t numCows);
    void feed();

    // Disable copying and assignment
    Barn(const Barn& other) = delete;
    Barn& operator=(const Barn& rhs) = delete;

 private:
    size_t cowCapacity_;
    Cow* residentCows_;  // Points to an array of Cow objects on the heap
};

Let's declare a local variable, Barn barney{4};.

How much memory will be allocated on the stack for barney?

Initializing Heap Memory

  • LHS Cow speaking

    If an instance of a class needs to use heap memory to store its values, then that memory will often be allocated in the constructor.

  • RHS Cow speaking

    Remember that whenever possible, we want to initialize memory in the member-initialization list!

  • LHS Cow speaking

    So we'll have to put a call to new inside the member-initialization list!

Let's focus on the following parameterized constructor for our Barn class, which has been very slightly updated to change the name of the second data member from residentCow_ (which was a Cow) to residentCows_ (which is a Cow*):

// Note: This code is not quite right yet!
Barn::Barn(size_t numCows) : cowCapacity_{numCows}, residentCows_{"Bessie", 42}
{
    // Nothing (else) to do here!
}
  • Horse speaking

    Hay, wait! If residentCows_ is a Cow*, then we can't initialize it like it's a Cow, can we?!

  • LHS Cow speaking

    You're right! We need to update our member-initialization list to reflect the new type of residentCows_.

What should go inside the curly braces in the member-initialization list to set the initial value for residentCows_?

Dropping that code into our member-initialization list, we get

Barn::Barn(size_t numCows) : cowCapacity_{numCows}, residentCows_{new Cow[cowCapacity_]}
{
    // Nothing (else) to do here!
}

You can explore the revised code by clicking the button below:

  • Cat speaking

    Hmm… It looks like we're using one data member (cowCapacity_) to initialize another data member (residentCows_)…

  • LHS Cow speaking

    Yes! We're totally allowed to do that.

  • RHS Cow speaking

    In fact, that's part of why C++ is so picky about us initializing variables in the same order they're listed in the class definition.

  • LHS Cow speaking

    It helps us make sure that values we might use in a later initialization are ready for us to use.

With the changes we've made to the Barn class, suppose we write (as we do in main),

Barn jaysPlace{4};

How many Cow objects will be initialized when the Barn constructor makes jaysPlace?

What's Not to Like…?

Right now, a Barn object always has cowCapacity_ Cow objects. That's because it has an array of Cows, which is just that many Cows all together in memory.

  • Cat speaking

    So what you're saying is, we have some flexibility now, in that we can change the number of cows, but it's still the case that we end up locked into that number afterwards.

  • LHS Cow speaking

    Right, and that's not great. It's also not great that they're all bland default cows.

  • Goat speaking

    Meh, I've always thought that arrays being a fixed size wasn't a great idea.

  • LHS Cow speaking

    We'll have to come up with a way to add more flexibility to our fixed-size array.

  • Hedgehog speaking

    By making things more complicated I expect?

  • LHS Cow speaking

    A little bit.

  • Horse speaking

    Hay! There is another problem. When our Barn had a single Cow, it was destroyed at the end. Now none of the Cows get destroyed!

  • LHS Cow speaking

    Good eye. That's a bug right there, a memory leak, in fact. We'll fix it, but it'll take us a while to get there.

(When logged in, completion status appears here.)