Aggregate TypesAggregate TypesAggregates
Fundamental Types |
void , bool , char , int , double , … |
Simple Aggregates |
|
More Complex Custom Types |
|
Example: Type point
with 2 integer coordinates
struct point {
int x; // ← "member variable"
int y;
};
// create new object (on stack)
point p {44, 55};
// read members' values
cout << p.x <<' '<< p.y; // 44 55
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 | |
⋮ |
Fundamental types are not default-initialized ⇒ members get whatever value was in memory at the time:
point u; // uninitialized
cout << u.x; // random value!
141 | STACK | |
u.y | -7682 | top |
u.x | 23988 | |
p.y | 20 | |
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
enum class month { jan=1, feb=2,… , dec=12 };
struct date {
int yyyy;
month mm;
int dd;
};
struct person {
std::string name;
date bday;
};
int main() {
person jlp { "Jean-Luc Picard", {2305, month::jul, 13} };
cout << jlp.name; // Jean-Luc Picard
cout << jlp.bday.dd; // 13
date yesterday { 2020, month::jun, 16 };
person rv = { "Ronald Villiers", yesterday };
}
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: copies refer to different objects; (member) values are copied
- deep assignment: target replicates source object's value
- 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++ (and Rust).
Reference Semantics
= variables are referencs to objects:
- shallow copying: copies of a variable refer to the same object
- shallow assignment: assignment makes a variable reference 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
- 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
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