CS 70

Revisiting the Default Constructor

Now that we have a way to create a Barn with a parameterized constructor, and to add Cows to it, let's revisit the default constructor we set aside earlier.

The default constructor needs to do all of the same work that the parameterized constructor does: initialize the data members of the Barn class, including allocating space on the heap for the array of Cow*s.

Crucially, there's no way to have a Barn if we don't know how many Cows it can hold. That means we have two choices:

Option 1: No Default Barns!

Our first option is the simplest, from the perspective of the person developing the Barn class: we make sizing a Barn the problem of anyone that wants to use the Barn class! If they don't tell us how big the Barn should be, they don't get to have a Barn object.

We can enforce that choice by disabling the default Barn constructor (just like we already did with the copy constructor and assignment operator):

class Barn {
 public:
    Barn(size_t numCows);
    void feed();
    void addCow(std::string cowName, size_t numSpots);
    void addCow(const Cow& cow);

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

 private:
    size_t cowCapacity_;  // The number of Cows the Barn can hold
    size_t cowCount_;     // The number of Cows in the Barn
    Cow** residentCows_;  // Points to an array of Cow*s on the heap
};

(We took this approach in the online version of the code you looked at on the previous page.)

Option 2: Default Barns Have a Default Size!

If we want to be a little friendlier to users of the Barn class, we could make it possible to have a default Barn.

In that case, we will need to decide how big the default Barn should be.

  • Watson speaking

    Let's be careful not to use magic numbers, though! We can store the default Barn size as a static (i.e., class-wide) constant instead!

class Barn {
 public:
    Barn();
    Barn(size_t numCows);
    void feed();
    void addCow(const Cow& toCopy);

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

 private:
    static constexpr size_t DEFAULT_SIZE = 640;
    size_t cowCapacity_;
    size_t cowCount_;
    Cow** residentCows_;
};

Now we can write the implementation of the default constructor:

Barn::Barn() :
    cowCapacity_{_____},
    cowCount_{0},
    residentCows_{new Cow*[cowCapacity_]}
{
    // Nothing (else) to do
}

What should go in the blank, to initialize cowCapacity_?

So our revised code is

Barn::Barn() :
    cowCapacity_{DEFAULT_SIZE},
    cowCount_{0},
    residentCows_{new Cow*[cowCapacity_]}
{
    // Nothing (else) to do
}
  • Cat speaking

    This looks a lot like our code for the parameterized constructor, so it feels like we're copying and pasting code.

  • LHS Cow speaking

    There is a nice way to avoid repeating that code, but it does mean we'll to go down a tiny rabbit hole to learn about a useful C++ feature.

  • Rabbit speaking

    There's lots of weird and wonderful C++ rabbit holes we could explore!

  • Goat speaking

    But they won't be on the test, right?

  • LHS Cow speaking

    No. But sometimes knowing these details can come in handy.

Delegating Constructors

In the place where we'd normally put a member-initialization list, we can instead write a constructor call to another constructor for our class.

So the revised code would be

Barn::Barn() : Barn{DEFAULT_SIZE}
{
    // Nothing (else) to do
}
  • Rabbit speaking

    There are lots more things down the rabbit hole of C++ features. We could have an Option 3, where we instead use C++'s default-argument mechanism to create our default constructor by allowing the agument to our parameterized constructor to be omitted.

  • Hedgehog speaking

    I'm sure you're very nice and trying to be helpful, but I have enough things in my head right now!

(When logged in, completion status appears here.)