Skip to content

Commit 035ee5d

Browse files
committed
Updating to C++23
1 parent 1cd9ff1 commit 035ee5d

File tree

1 file changed

+54
-56
lines changed

1 file changed

+54
-56
lines changed

09-classes-friends-and-polymorphism.md

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,29 @@ The key to solving the inability to create `Person`s using uniform initializatio
3434
```cpp
3535
// 09-person1.cpp : model Person as a class with constructor
3636
37+
#include <chrono>
3738
#include <iostream>
3839
#include <string>
3940
#include <string_view>
4041
using namespace std;
41-
42-
struct Date {
43-
int year{}, month{}, day{};
44-
};
42+
using namespace std::chrono;
4543
4644
class Person {
4745
public:
48-
Person(const Date& dob, string_view familyname, string_view firstname)
46+
Person(const year_month_day& dob, string_view familyname, string_view firstname)
4947
: dob{ dob }, familyname{ familyname }, firstname{ firstname }
5048
{}
5149
string getName() const { return firstname + ' ' + familyname; }
50+
const year_month_day& getDob() const { return dob; }
5251
private:
53-
const Date dob;
52+
const year_month_day dob;
5453
string familyname, firstname;
5554
};
5655
5756
5857
int main() {
59-
Person genius{ { 1879, 3, 14 }, "Einstein", "Albert" };
60-
cout << genius.getName() << '\n';
58+
Person genius{ { 1879y, March, 14d }, "Einstein", "Albert" };
59+
cout << genius.getName() << " was born " << genius.getDob() << '\n';
6160
}
6261
```
6362

@@ -69,19 +68,23 @@ Quite a few things to note about this program:
6968

7069
* The member variables are initialized using uniform initialization syntax; this forbids narrowing conversions, and there shouldn't be any as the parameter types should have been carefully chosen. (Older code may use parentheses here instead of braces.) The order of construction is the same as the way the member fields are laid out (after the `private:` access specifier); the order in the comma-separated initializers is unimportant (although you should try to replicate the order of the member fields, your compiler will warn if they differ). The constructor's body is empty here (although it must be present), and this is not unusual.
7170

72-
* The `Date` parameter is passed as `const`-reference instead of by value, as it is probably too big to fit in a single register to pass by value. The names are passed by value as `std::string_view` although in older code `const std::string&` would be common.
71+
* The `std::chrono::year_month_day` parameter (itself initialized by uniform initialization) is passed as `const`-reference instead of by value, as it is probably too big to fit in a single register to pass by value. The names are passed by value as `std::string_view` although in older code `const std::string&` would be common.
7372

7473
* The member function `getName()` is declared `const` as it is guaranteed not to change any member variables. It returns a newly created `std::string` which must be returned by value.
7574

76-
* The member variable `date` is declared `const` as it will never need to be changed; of course it needs to be initialized by the constructor, but this is allowed. The member variables `familyname` and `firstname` need to be of type `std::string` (not `std::string_view` as for the constructor's parameters) for them to be guaranteed to exist for the lifetime of the class.
75+
* The member variable `dob` is declared `const` as it will never need to be changed; of course it needs to be initialized by the constructor, and this case is allowed. The member variables `familyname` and `firstname` need to be of type `std::string` (not `std::string_view` as for the constructor's parameters) for them to be guaranteed to exist for the lifetime of the class.
76+
77+
* The member function `getDob()` is also declared `const` and returns a `const`-reference. It is possible to put this return value directly to a `std::ostream` as the Standard Library overloads `operator<<` for `std::chrono::year_month_day`.
7778

7879
**Experiment:**
7980

8081
* Add more `Person` variables to `main()`, and output their names.
8182

8283
* Rewrite the constructor to initialize the member variables in the body, instead of using the comma-separated list of member initializers.
8384

84-
* Write getters (all declared `const`) called `getFamilyName()`, `getFirstName()`, `getDOB()` avoiding creation of unnecessary temporary variables. Modify `main()` to use these.
85+
* Modify this program to use `std::println()` instead of `cout`.
86+
87+
* Write getters (all declared `const`) called `getFamilyName()` and `getFirstName()` avoiding creation of unnecessary temporary variables. Modify `main()` to use these.
8588

8689
* Write setters called `setFamilyName()` and `setFirstName()`. Test these from `main()` again.
8790

@@ -100,17 +103,18 @@ The following program defines three `class`es, the second and third of which der
100103
```cpp
101104
// 09-person2.cpp : model Person, Student and Employee as a class inheritance hierarchy
102105
106+
#include <chrono>
103107
#include <iostream>
104108
#include <string>
105109
#include <string_view>
106110
#include <vector>
107111
using namespace std;
112+
using namespace std::chrono;
108113
109114
class Person {
110115
public:
111-
struct Date;
112-
Person(Date dob) : dob{ dob } {}
113-
Person(Date dob, string_view familyname, string_view firstname, bool familynamefirst = false)
116+
Person(year_month_day dob) : dob{ dob } {}
117+
Person(year_month_day dob, string_view familyname, string_view firstname, bool familynamefirst = false)
114118
: dob{ dob }, familyname{ familyname }, firstname{ firstname },
115119
familynamefirst{ familynamefirst } {}
116120
virtual ~Person() {}
@@ -128,12 +132,8 @@ public:
128132
return firstname + ' ' + familyname;
129133
}
130134
}
131-
struct Date {
132-
unsigned short year{};
133-
unsigned char month{}, day{};
134-
};
135135
protected:
136-
const Date dob;
136+
const year_month_day dob;
137137
private:
138138
string familyname, firstname;
139139
bool familynamefirst{};
@@ -144,7 +144,7 @@ public:
144144
enum class Schooling;
145145
Student(const Person& person, const vector<string>& attended_classes = {}, Schooling school_type = Schooling::preschool)
146146
: Person{ person }, school_type{ school_type }, attended_classes{ attended_classes } {}
147-
const Date& getDOB() const { return dob; }
147+
const year_month_day& getDob() const { return dob; }
148148
const vector<string>& getAttendedClasses() const { return attended_classes; }
149149
enum class Schooling { preschool, elementary, juniorhigh, highschool, college, homeschool, other };
150150
private:
@@ -156,7 +156,7 @@ class Employee : public Person {
156156
public:
157157
Employee(const Person& person, int employee_id, int salary = 0)
158158
: Person{ person }, employee_id{ employee_id }, salary{ salary } {}
159-
bool isBirthday(Date today) const { return dob.month == today.month && dob.day == today.day; }
159+
bool isBirthdayToday(year_month_day today) const { return dob.month() == today.month() && dob.day() == today.day(); }
160160
void setSalary(int salary) { salary = salary; }
161161
auto getDetails() const { return pair{ employee_id, salary }; }
162162
private:
@@ -165,7 +165,7 @@ private:
165165
};
166166
167167
int main() {
168-
Person genius{ { 1879, 3, 14 }, "Einstein", "Albert" };
168+
Person genius{ { 1879y, March, 14d }, "Einstein", "Albert" };
169169
Student genius_student{ genius, { "math", "physics", "philosophy" }, Student::Schooling::other };
170170
Employee genius_employee{ genius, 1001, 15000 };
171171
@@ -179,25 +179,21 @@ int main() {
179179
180180
auto [ id, salary ] = genius_employee.getDetails();
181181
cout << "ID: " << id << ", Salary: $" << salary << '\n';
182-
Person::Date next_bday{ 2020, 3, 14 };
183-
if (genius_employee.isBirthday(next_bday)) {
182+
year_month_day next_bday{ 2023y, March, 14d };
183+
if (genius_employee.isBirthdayToday(next_bday)) {
184184
cout << "Happy Birthday!\n";
185185
}
186186
}
187187
```
188188
189189
Many things to note about this program:
190190
191-
* The `Date` type has been moved to be inside the `Person`; its fully qualified name is therefore `Person::Date`; this has been done to illustrate how `struct`s and `class`es and be nested inside each other. (The type `std::year_month_day`, new to C++20, was not available in my compiler when this program was written.) A forward-declaration `struct Date;` is necessary to avoid having to define `Date` in full before the first constructor.
191+
* A second constructor for `Person` taking only a `std::chrono::year_month_day` has been added. Setters can be used later to initialize or modify the other three member variables, which are left defaulted by this constructor (empty for the two `std::string`s and `false` for the `bool`).
192192
193-
* A second constructor for `Person` taking only a `Date` has been added. Setters can be used later to initialize or modify the other three member variables, which are left defaulted by this constructor (empty for the two `std::string`s and `false` for the `bool`).
194-
195-
* A `virtual` destructor has been addded to `Person`; if you remember one thing about inheritance, it should be that base classes need a virtual destructor. This is so that any heap objects of type `Student` or `Employee` assigned to a pointer of type `Person*` (including use of smart pointers), the correct destructor of the **derived** class can be found and thus called, avoiding memory leaks.
193+
* A `virtual` destructor has been added to `Person`; if you remember one thing about inheritance, it should be that base classes need a virtual destructor. This is so that any heap objects of type `Student` or `Employee` assigned to a pointer of type `Person*` (including use of smart pointers), the correct destructor of the **derived** class can be found and thus called, avoiding memory leaks.
196194
197195
* The `getName()` function returns the name(s) provided by either the constructor or the setter(s) as a single `std::string`, ordered according to the member variable `familynamefirst`. (I hope this attempt at cultural inclusion doesn't offend anyone!)
198196

199-
* The `Date` type has been modified to try to fit it into 32-bits, so it is almost certainly passed more efficiently by value in a single register rather than by `const`-reference.
200-
201197
* The member variable `dob` is declared `protected:`, the other three are `private:`, as before.
202198

203199
* The `Student` type is derived from `Person` using the keyword `public`. If this keyword were omitted, none of `Person`'s `public:` members would be visible to users of `Student`, as `class`es default to private inheritance. The syntax is exactly as for `Pixel` inheriting from `Point` in Chapter 6.
@@ -208,13 +204,13 @@ Many things to note about this program:
208204
209205
* The base class portion of `Student` is initialized as `Person{ person }` where `person` is of type `const Person&`. Then the other two fields of `Student` are initialized. The constructor parameter variable `attended_classes` is passed as a `const vector<string>&` so that only one copy is made, which is when the member variable of the same name is initialized.
210206
211-
* A `public:` member function `getDOB()` makes the `protected:` member of the base class `dob` available to **users** of the derived class. It is declared `const` and returns a `const`-reference.
207+
* A `public:` member function `getDob()` makes the `protected:` data member of the base class `dob` available to **users** of the derived class, in this case `Student`. It is declared `const` and returns a `const`-reference.
212208
213209
* The member function `getAttendedClasses()` returns a `const`-reference to `attended_classes`, therefore this `std::vector<string>` is made visible to the function which calls this member function, but is not modifiable.
214210
215211
* The `Employee` constructor takes three parameters, the third of which is optional. The base class portion is initialized in the same way as for `Student`.
216212
217-
* The member function `isBirthday()` takes a `Person::Date` as a parameter and compares the `day` and `month` fields with those of `dob`, returning `true` if they are the same, or `false` otherwise. (We're pretending "today" is March 14, 2020.)
213+
* The member function `isBirthdayToday()` takes a `std::chrono::year_month_day` as a parameter and compares the return values of the `day()` and `month()` members with those of `dob`, returning `true` if they are the same, or `false` otherwise. (We're pretending "today" is March 14, 2024, so this function always returns `true`.)
218214

219215
* The member variable `employee_id` is not meant to be able to be changed, so is declared `const`. The setter `setSalary()` is defined so that `salary` can be updated, while the getter `getDetails()` returns an aggregate of both derived class member variables by value.
220216

@@ -232,7 +228,7 @@ Many things to note about this program:
232228

233229
* Write a second constructor for `Employee` to accomplish the same thing.
234230

235-
* Add `getDOB()` to `Employee`, as for `Student`. Now try to add it to `Person`, what do you find? Would a single `public:` getter in the base class be more useful than a `protected:` member variable?
231+
* Add `getDob()` to `Employee`, as for `Student`. Now try to add it to `Person`, what do you find? Would a single `public:` getter in the base class be more useful than a `protected:` member variable?
236232

237233
* Add member functions `addAttendedClass()` and `removeAttendedClass()` to `Student`. Make them smart enough to handle duplicates/invalid parameters.
238234

@@ -327,47 +323,39 @@ A couple of things to note:
327323
Friends have access to all members of the `class` that declares them a `friend`, including those declared `private:` or `protected:`. Sometimes this is desirable, as shown in the following program:
328324

329325
```cpp
330-
// 09-person3.cpp : define operator== and operator<< for Person class
326+
// 09-person3.cpp : define operator<=> for Person class
331327

332328
#include <iostream>
333329
using namespace std;
334330

335331
struct Date {
336332
int year{}, month{}, day{};
333+
auto operator<=>(const Date&) const = default;
337334
};
338335

339-
bool operator== (const Date& lhs, const Date& rhs) {
340-
return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day;
341-
}
342-
343336
class Person {
344337
public:
345338
Person(const Date& dob, string_view familyname, string_view firstname)
346339
: dob{ dob }, familyname{ familyname }, firstname{ firstname }
347340
{}
348341
string getName() const { return firstname + ' ' + familyname; }
349-
friend bool operator== (const Person&, const Person&);
342+
const auto& getDob() const { return dob; }
343+
auto operator<=>(const Person&) const = default;
350344
friend ostream& operator<< (ostream&, const Person&);
351345
private:
352-
const Date dob;
353346
string familyname, firstname;
347+
const Date dob;
354348
};
355349

356-
bool operator== (const Person& lhs, const Person& rhs) {
357-
return lhs.familyname == rhs.familyname
358-
&& lhs.firstname == rhs.firstname
359-
&& lhs.dob == rhs.dob;
360-
}
361-
362350
ostream& operator<< (ostream& os, const Person& p) {
363351
os << "Name: " << p.getName() << ", DOB: "
364352
<< p.dob.year << '/' << p.dob.month << '/' << p.dob.day;
365353
return os;
366354
}
367355

368356
int main() {
369-
Person person1{ { 2000, 1, 1 }, "John", "Doe" },
370-
person2{ { 1987, 11, 31 }, "John", "Doe" };
357+
Person person1{ { 2000, 1, 1 }, "Doe", "John" },
358+
person2{ { 1987, 11, 31 }, "Doe", "John" };
371359
cout << "person1: " << person1 << '\n';
372360
cout << "person2: " << person2 << '\n';
373361
if (person1 == person2) {
@@ -376,16 +364,28 @@ int main() {
376364
else {
377365
cout << "Different person!\n";
378366
}
367+
368+
cout << "person1 is ";
369+
if (person1.getDob() > person2.getDob()) {
370+
cout << "younger than ";
371+
}
372+
else if (person1.getDob() < person2.getDob()) {
373+
cout << "older than ";
374+
}
375+
else {
376+
cout << "the same age as ";
377+
}
378+
cout << "person2" << '\n';
379379
}
380380
```
381381

382382
Some things to note about this program:
383383

384-
* Global `operator==` is defined for `Date`. Note that if we used either `std::year_month_day` or `operator<=>` (both new in C++20, but not covered in this Tutorial), this definition would not be necessary. As this `Date` is a `struct` with all members `public:`, use of the keyword `friend` is not needed.
384+
* Member `operator<=>` (the "spaceship operator") is defaulted for this roll-your-own `Date`; this is all that is needed for the equality and ordering comparisons to be defined for this class, with ordering performed member-wise starting with the first data member.
385385

386-
* Within the definition of `Person`, both global `operator==` and global `operator<<` are declared `friend`. This is more boilerplate that you can use in your own classes, changing all occurrences of `Person` to the name of your class. (They are identical to normal function declarations, other than the use of the `friend` keyword.)
386+
* Within the definition of `Person`, global `operator<<` is declared as a `friend` function. This is more boilerplate that you can use in your own classes, changing parameter `const Person&` to the name of your class. (They are identical to normal function declarations, other than the use of the `friend` keyword.)
387387

388-
* Global `operator==` is defined for `Person`. Here the `std:::string` members are compared explicitly, before the `Date` members are compared, calling the previously defined `operator==` for `Date` automatically.
388+
* Member `operator<=>` is defaulted for `Person`; with this code the `std:::string` members will be compared (`familyname` before `firstname`), before the `Date` members are compared.
389389

390390
* Global `operator<<` is also defined for `Person`, allowing objects to be put to `cout` (and any other `std::ostream`s) using `<<`. This needs to be a `friend` because it accesses `dob`.
391391

@@ -395,11 +395,9 @@ Some things to note about this program:
395395

396396
* Now give them different names. What output do you get?
397397

398-
* Define global `operator<<` for `Date`. Can you remove the need for `operator<<` for `Person`, to itself be a `friend` of `class Person`?
399-
400-
* Make global `operator==` for `Person` compare `getName()`s. Can you remove the need for it to be a `friend`?
398+
* Define global `operator<<` for `Date`. Can you remove the need for `operator<<` for `Person` to itself be a `friend` of `class Person`?
401399

402-
* Make `operator==` for `Person` a member function instead of a global function.
400+
* Compare a few `Person` instances with similar or same family names and first names, storing them in a `std::set<Person>`. Write code to output them telephone-book style. Are they ordered in the way you would expect?
403401

404402
Classes can be declared `friend`s as well as functions, although this use is probably less common. The following program defines two `class`es `A` and `B` which are mutual friends, thus allowing member functions of either to access each other's `private:` members.
405403

@@ -628,4 +626,4 @@ A lot of things to note about this program:
628626

629627
[^1]: Grady Booch, Robert A. Maksimchuk, Michael W. Engle, Bobbi J. Young, Jim Conallen, Kelli A. Houston *Object-Oriented Analysis and Design with Applications* (3rd ed. Pearson, 2007, ISBN-13: 9780201895513)
630628

631-
*All text and program code &copy;2019-2022 Richard Spencer, all rights reserved.*
629+
*All text and program code &copy;2019-2024 Richard Spencer, all rights reserved.*

0 commit comments

Comments
 (0)