Aggregate Types Aggregate Types Aggregates
Fundamental Types |
void, bool, char, int, double, … |
Simple Aggregates |
Main Purpose: grouping data
|
More Complex Custom Types |
Main Purpose: enabling correctness/safety guarantees
|
Example: Type with 2 integer coordinates
#include <iostream>
struct point {
int x; // ← "member variable"
int y;
};
int main() {
// create new object (on stack)
point p {44, 55};
// print members' values
std::cout << p.x <<' '<< p.y << '\n';; // 44 55
p.x = 10;
p.y = 20;
std::cout << p.x <<' '<< p.y << '\n';
point u;
std::cout << u.x <<' '<< u.y << '\n';
}
Member variables are stored in the same order as they are declared.
| -7682 | STACK | |
| 23988 | ||
| p.y | 55 | top |
| p.x | 44 | |
| ⋮ |
Assigning to member values:
p.x = 10;
p.y = 20;
cout << p.x <<' '<< p.y; // 10 20
| -7682 | STACK | |
| 23988 | ||
| p.y | 20 | top |
| p.x | 10 | |
| ⋮ |
Why Custom Types / Data Aggregation? Why Custom Types? Why?
Interfaces become easier to use correctly
- semantic data grouping:
point,date, … - avoids many function parameters and thus, confusion
- can return multiple values from function with one dedicated type
instead of multiple non-const reference
output parameters
Without: Horrible Interfaces!
void closest_point_on_line (double lx2, double ly1, double lx2i, double ly2, double px, double py, double& cpx, double& cpy) { … }
- many parameters of same type ⇒ easy to write bugs
- non-const reference output parameters ⇒ error-prone
- internal representation of a line is also baked into the interface
With: A Lot Better!
struct point { double x; double y; };
struct line { point a; point b; };
point closest_point_on_line (line const& l, point const& p) { … }
- straight-forward interface
- easy to use correctly
- If internal representation of line changes (e.g., point + direction instead of 2 points)
⇒ implementation of
needs to be changed too, but its interface can stay the same ⇒ most of calling code doesn't need to change!closest_point_on_line
Type { arg1, arg2, …, argN }
- brace-enclosed list of member values
- in order of member declaration
enum class month {jan = 1, feb = 2,…, dec = 12};
struct date {
int yyyy;
month mm;
int dd;
};
int main () {
date today {2020, month::mar, 15};
// C++98, but also still OK:
date tomorrow = {2020, month::mar, 16};
}
Compounds
Example: date as member of person
#include <iostream>
#include <string>
enum class month { jan=1, feb=2, mar=3, apr=4, may=5, jun=6, jul=7, aug=8, sep=9, oct=10, nov=11… , dec=12 };
struct date {
int yyyy;
month mm;
int dd;
};
struct person {
std::string name;
date bday;
};
int main () {
using std::cout;
person jlp { "Jean-Luc Picard", {2305, month::jul, 13} };
cout << jlp.name << '\n'; // Jean-Luc Picard
cout << jlp.bday.dd << '\n'; // 13
date yesterday { 2020, month::jun, 16 };
person rv = { "Ronald Villiers", yesterday };
cout << rv.name << ", born "
<< rv.bday.yyyy <<'-'<< int(rv.bday.mm) <<'-'<< rv.bday.dd << '\n';
}
Copies are always deep copies of all members!
enum class month {jan = 1, … };
struct date {
int yyyy; month mm; int dd;
};
int main () {
date a {2020, month::mar, 7};
date b = a; // deep copy of a
b.dd = 22; // change b
}
State after last line of main:
| STACK | ||
| b.dd | 22 | top |
| b.mm | 3 | |
| b.yyyy | 2020 | |
| a.dd | 7 | |
| a.mm | 3 | |
| a.yyyy | 2020 | |
| ⋮ |
- Copy Construction = create new object with same values as source
- Copy Assignment = overwrite existing object's values with that of source
struct point { int x; int y; };
point p1 {1, 2}; // construction
point p2 = p1; // copy construction
point p3 ( p1 ); // copy construction
point p4 { p1 }; // copy construction
auto p5 = p1; // copy construction
auto p6 ( p1 ); // copy construction
auto p7 { p1 }; // copy construction
p3 = p2; // copy assignment
// (both p2 & p3 existed before)
Value Semantics
= variables refer to objects themselves:
- deep copying: produces a new, independent object; object (member) values are copied
- deep assignment: makes value of target equal to that of source object
- deep ownership: member variables refer to objects with same lifetime as containing object
- value-based comparison: variables compare equal if their values are equal
Value semantics is the default behavior for fundamental types
(int, double, etc.) in almost all programming languages
and also the default for aggregates/user-defined types in C++.
Reference Semantics
= variables are references to objects:
- shallow copying: copies of a variable refer to the same object
- shallow assignment: assignment makes a variable refer to a different object
- shallow ownership: member variables are also just references
- identity-based comparison: variables compare equal if they refer to the same object
Most other mainstream languages (Java, Python, C#, Swift, …) use (baked-in) reference semantics for user-defined types.
The situation in C++ is consistent and offers full control:
- default: value semantics for all types (except C-style arrays)
- optional reference semantics possible for all types (by using references or pointers)
std::vector of Aggregates
vector of Aggregates
inside std::vector
Value Semantics ⇒
vector<T>'s storage contains objects of typeTthemselves, not justreferences
orpointers
to them (as in Java/C#/…)- if vector object gets destroyed
⇒ contained
Tobjects get destroyed
vector<int> v { 0,1,2,3,4 };
struct p2d { int x; int y; };
vector<p2d> v {{1,2},{5,6},{8,9}};
The Most Vexing Parse
Can't use empty parentheses for object construction due to an ambiguity in C++'s grammar:
struct A { … };
A a (); // declares function 'a'
// without parameters
// and return type 'A'
A a; // constructs an object of type A
A a {} // constructs an object of type A
Comments…