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…