Before You Start
We've come quite a long way. So far, we've learned:
- How to make classes in C++.
- How to define our own versions of C++ operators for our class to make our classes look and behave like C++'s built-in
- Examples:
=,<<, and even[.index ]
- Examples:
- How to allocate memory dynamically on the heap.
- Either single objects or arrays.
- The Maze assignment showed us that
- Successively doubling is a good way to handle growing arrays.
- What things we need to be careful about when growing objects.
- Two ways to loop over an array:
- Using an integer index.
- Start/past-the-end pointers.
Looking Back at Our CheckList Class
At the end of our last lesson, we'd made a CheckList class with this header:
#ifndef CHECKLIST_HPP_INCLUDED
#define CHECKLIST_HPP_INCLUDED
#include <string>
#include <iostream>
class CheckList {
public:
CheckList() = delete;
CheckList(std::string task, size_t numSteps);
CheckList(const CheckList& other);
~CheckList();
CheckList& operator=(CheckList rhs); // copy-and-swap idiom
void swap(CheckList& other);
std::string task() const;
size_t size() const;
// We provide two overloaded operator[], one for writable checklists
// and one for read-only ones.
std::string& operator[](size_t index);
const std::string& operator[](size_t index) const;
// We provide two overloaded begin/end functions one for writable checklists
// and one for read-only ones.
std::string* begin();
std::string* end();
const std::string* begin() const;
const std::string* end() const;
void printToStream(std::ostream& out) const;
private:
std::string task_;
size_t numSteps_;
std::string* steps_;
};
// We'll provide a global function that adds two checklists
CheckList operator+(const CheckList& lhs, const CheckList& rhs);
// We'll provide a global function that prints a checklist.
std::ostream& operator<<(std::ostream& out, const CheckList& list);
#endif // CHECKLIST_HPP_INCLUDED
You can explore the rest of the code and run it on OnlineGDB.
Or if you like, you can view it here:
Other files
checklist.cpp:
#include "checklist.hpp"
#include <string>
CheckList::CheckList(std::string task, size_t numSteps)
: task_{task}, numSteps_{numSteps}, steps_{new std::string[numSteps_]} {
// Array of strings on heap has all elements default initialized to
// empty strings, which is fine, so nothing (else) to do.
}
CheckList::CheckList(const CheckList& other)
: task_{other.task_},
numSteps_{other.numSteps_},
steps_{new std::string[numSteps_]} {
// Rather than hand-write a for loop, this code uses std::copy to copy
// the array. The arguments are:
// source_start, source_past_end, dest_start
std::copy(other.steps_, other.steps_ + other.numSteps_, steps_);
}
CheckList::~CheckList() {
delete[] steps_;
}
CheckList& CheckList::operator=(CheckList rhs) { // copy-and-swap idiom
swap(rhs);
return *this;
}
void CheckList::swap(CheckList& other) {
std::swap(task_, other.task_);
std::swap(numSteps_, other.numSteps_);
std::swap(steps_, other.steps_);
}
size_t CheckList::size() const {
return numSteps_;
}
std::string CheckList::task() const {
return task_;
}
std::string& CheckList::operator[](size_t index) {
return steps_[index];
}
const std::string& CheckList::operator[](size_t index) const {
return steps_[index];
}
std::string* CheckList::begin() {
return steps_;
}
std::string* CheckList::end() {
return steps_ + numSteps_;
}
const std::string* CheckList::begin() const {
return steps_;
}
const std::string* CheckList::end() const {
return steps_ + numSteps_;
}
void CheckList::printToStream(std::ostream& out) const {
out << task_ << ":" << std::endl;
for (std::string* p = steps_; p != steps_ + numSteps_; ++p) {
out << " - " << *p << std::endl;
}
}
CheckList operator+(const CheckList& lhs, const CheckList& rhs) {
CheckList result{lhs.task() + " & " + rhs.task(), lhs.size() + rhs.size()};
std::copy(lhs.begin(), lhs.end(), result.begin());
std::copy(rhs.begin(), rhs.end(), result.begin() + lhs.size());
return result;
}
std::ostream& operator<<(std::ostream& out, const CheckList& list) {
list.printToStream(out);
return out;
}
checklist-test.cpp:
#include "checklist.hpp"
#include <algorithm>
#include <iostream>
#include <string>
int main() {
CheckList makeDinner{"Sausage and Mash", 7};
makeDinner[0] = "Peel potatoes";
makeDinner[1] = "Cook potatoes";
makeDinner[2] = "Cook vegetables";
makeDinner[3] = "Grill Sausages";
makeDinner[4] = "Make Gravy";
makeDinner[5] = "Mash Potatoes";
makeDinner[6] = "Serve";
// Now we use the output operator, rather than printToSteam
std::cout << makeDinner << std::endl;
CheckList makeBevs{"Iced Tea", 3};
makeBevs[0] = "Brew Tea";
makeBevs[1] = "Chill";
makeBevs[2] = "Serve";
std::cout << makeBevs << std::endl;
// Here we use a plus operator that combines two checklists into
// one.
CheckList makeAll = makeDinner + makeBevs;
// Using the begin/past-the-end pointers, we can call built-in C++
// algorithms that work on arrays.
std::sort(makeAll.begin(), makeAll.end());
// We can make a for loop that iteraters over all the items in our
// checklist using begin/past-the-end style, much like we would for
// a built-in array. Here we go through all our check-list items
// and add a question mark.
for (std::string* p = makeAll.begin(); p != makeAll.end(); ++p) {
std::string& checkItem = *p;
checkItem += '?';
}
// And, even cooler, if we have .begin() and .end(), we can use a NEW
// KIND of for loop that works just like the loop above, but hides
// the details of calling .begin() and .end() and the pointer.
// Here we go through all our check-list items and add an exclamation
// mark.
for (std::string& checkItem : makeAll) {
checkItem += '!';
}
std::cout << makeAll << std::endl;
}
The begin() and end() member functions return a pointer to the internal array of CheckList items, allowing us to go through them with a standard begin/past-the-end loop.
The begin() and end() member functions are overloaded based on whether the object they're being invoked on is const (i.e., read-only) or not.
In other words, if we have a CheckList object called myList,
- If
myListisconst(read-only), and we saymyList.begin(), it calls theconstversion ofmyList.begin(), which returns aconst std::string*(which doesn't allow us to change the checklist items). - If
myListis non-const(can be written to),myList.begin()will call the non-constversion ofbegin*()that returns astd::string*(which can be used to both read and write a checklist item).
I know we've done it before, but it still seems weird and confusing to put
constat the end of the line, not attached to anything, just sort of dangling there….
That's how we mark the target object (the object that
thispoints to) asconst.
It is a bit of an odd placement, but there's nowhere else the C++ standard could have put it. A
constat the front of the line specifies the return type, and aconstinside the parentheses would mark one of the explicit arguments as beingconst.
(When logged in, completion status appears here.)