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 typeT
themselves, not justreferences
orpointers
to them (as in Java/C#/…)- if vector object gets destroyed ⇒ contained
T
objects 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…