Revisiting the Default Constructor
Now that we have a way to create a Barn
with a parameterized constructor, and to add Cow
s 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 Cow
s 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.
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
}
So our revised code is
Barn::Barn() :
cowCapacity_{DEFAULT_SIZE},
cowCount_{0},
residentCows_{new Cow*[cowCapacity_]}
{
// Nothing (else) to do
}
This looks a lot like our code for the parameterized constructor, so it feels like we're copying and pasting code.
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.
There's lots of weird and wonderful C++ rabbit holes we could explore!
But they won't be on the test, right?
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
}
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.
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.)