week4OOP
week4OOP
30-R-2025W
Advanced
Object-Oriented
Systems Design
Using C++
Object-Oriented Programming
A copy constructor is a
constructor:
• with a T const& argument,
and,
• where T is the name of the class, struct, or union it is in
• is invoked when the compiler/programmer needs to create and
copy the data of another instance of the same type into the
created type.
Constructors (con’t)
w03/copy-constructor.cxx
1 struct Type
2 {
3 int* p;
4 // ...
5 Type(Type const& t) : // invoke constructors and initialize members after : before {
6 p(new int(*t.p)) // construct int* with the result of new int(42)
7 { // after { all members have been constructed
8 }
9 //...
10 };
Constructors (con’t)
w03/destructor.cxx
1 struct Type
2 {
3 int* p;
4 // ...
5 ~Type()
6 {
7 delete p; // free memory associated with p
8 }
9 // ...
10 };
A copy assignment
Copy and
operator:
Move • is a member
Assignment assignment operator
Operators overload
• with a single T const&
argument.
Copy and Move Assignment Operators (con’t)
w03/copy-assignment.cxx
1 #include <utility> // for std::swap since C++11, <algorithm> before C++11
2 struct Type {
3 int* p;
4 Type(int const& i) : p(new int{i}) { }
5 Type(Type const& t) : p(new int{*t.p}) { }
6 Type& operator =(Type const& t) // copy assignment operator
7 {
8 Type local(t); // create a local copy of t
9 std::swap(p,local.p); // exchange pointers
10 return *this; // local will now be destroyed
11 }
12 };
13 int main()
14 {
15 Type a(42), b{52}, c = 62; // a and b invoke Type(int const&) constructor
16 a = b = c; // copy assignment
17 }
Copy and Move Assignment Operators (con’t)
An X instance X vtable
Slot 0: &X::f
vtblptr
x Slot 1: &X::g
Slot 2: &X::h
27 / 83
struct/class Layout: Virtual Member Implications
(con’t)
Example Y:
w03/layout-egbY.cxx
1 // struct X from previous example
2 struct Y : X
3 {
4 int y;
5 void g(char) override;
6 virtual void q(Y const&);
7 };
A Y instance Y vtable
Slot 0: &X::f
vtblptr
Slot 1: &Y::g
x
y Slot 2: &X::h
Slot 3: &Y::q
struct/class
Layout: Virtual Member
Implications (con’t)
Example Z:
w03/layout-egbZ.cxx
1 // struct X from previous example
2 // struct Y from previous example
3 struct Z : Y
4 {
5 int z;
6 void h(float) override;
7 virtual void r(Z&);
8 };
Z vtable
A Z instance
Slot 0: &X::f
vtblptr
Slot 1: &Y::g
x
Slot 2: &Z::h
y
Slot 3: &Y::q
z
Slot 4: &Z::r
struct/class
Layout: Virtual Member
Implications (con’t)
Example Y2:
w03/layout-egbY2.cxx
1 // struct X from previous example
2 struct Y2 : X
3 {
4 int y2;
5 using X::g; // Unhide X members named g
6 virtual void g(double); // F0. Different signature!
7 };
A Y2 instance Y2 vtable
Slot 0: &X::f
vtblptr
x Slot 1: &X::g
y2 Slot 2: &X::h
Slot 3: F0
struct/class
Layout: Virtual Member
Implications (con’t)
Example W:
w03/layout-egbW.cxx
1 struct W
2 {
3 int w;
4 virtual void f(); // F1
5 void f(long); // F2
6 virtual void f(int); // F3
7 virtual void f(double); // F4
8 };
A W instance W vtable
Slot 0: F1
vtblptr
w Slot 1: F3
Slot 2: F4
A W2 instance W2 vtable
Slot 0: F5
vtblptr
w Slot 1: F3
Slot 2: F4
w2
Slot 3: F6
struct/class Layout: Non-Virtual Inheritance
• The memory layout of virtually inherited types must ensure that the
inheriting type will only have a single instance of each virtually
inherited type at that level otherwise all other aspects are the same as non-
virtual inheritance.
• Consider:
• w03/layout-ege1a.cxx
1 // Only A is inherited virtually below!
2 struct Z { int z; };
3 struct Y { int y; };
4 struct A : Z { int a; };
5 struct B : virtual A { int b; };
6 struct C : virtual A, Y { int c; };
7 struct D : B, C { int d; };
struct/class Layout: Virtual Inheritance (con’t)
• In terms of data members —not memory layout— D is equivalent to:
• w03/layout-ege1b.cxx
1 struct D_data_members
2 {
3 int z; int a; // D::A
4 int b; // D::B
5 int y; int c; // D::C
6 int d; // D
• 7 };
• since in the hierarchy both B and C virtually inherit from A and both B
and C are siblings at that level in the hierarchy.
• IMPORTANT: There is only one instance of A.
struct/class Layout: Virtual Inheritance (con’t)
A::a
A* B:: ptr1
B::b
A* C:: ptr2
Y::y
C::c
D::d
struct/class Layout: Virtual Inheritance
(con’t)
Does multiple virtual inheritance look complicated?
This is one of the reasons why most OOP languages don’t support general
multiple inheritance.
Some OOP languages do support limited multiple inheritance, e.g.,
• no data members,
• declared but not defined function members, and/or
• all inheritance must be virtual which eliminates some layout and access
issues.
struct/class Layout: Virtual Inheritance (con’t)
• But realize that in the worst case accesses to A’s and Z’s members
through B, C, and/or D are now via an indirection/computed memory
offset.
struct/class Layout: Virtual Inheritance (con’t)
i.e., invoking A’s constructor in D’s initialization list resolves the ambiguity.
NOTE: Note that A’s int is not a value that B’s or C’s constructor would have
set it to!
struct/class Layout: Virtual Inheritance
(con’t)
Ambiguous Member Function Case…
This case is resolved in one of two ways:
• by qualifying the call using the scope resolution operator —done the
same way as qualified data member access, or,
• by overriding the member function in the type where it is
ambiguous and setting the code for that function to properly invoke the
correct base/bases member function(s).
struct/class Layout: In General
If C++ compilers can determine that a specific virtual function or data member
in some virtual base will always be directly called, then it will emit code to
directly call/access it.
• When writing OOP polymorphic code, don’t assume that these cases will
occur.
Advice: If you are not writing OOP (i.e., run-time) polymorphic code,
then seriously ask yourself why you are using virtual in your code.
struct/class Layout: Overhead
One (1) additional indirection, i.e., the indirect function call itself, is needed if the
offset is not stored in the vtable.
• Pro: Fast since member offset pointer is extremely likely to be on the same L1
cache page as the instance.
• Con: RAM to store the offsets in each instance plus the constructor overhead
to set such in each instance.
struct/class Layout: Overhead (con’t)
Two (2) additional indirections are needed if the offset is stored in the vtable.
• Pro: Only one vtable pointer in each instance is required: constructors
need only set one pointer.
• Con: Extra cache page and indirection.
• The first indirection is to access the vtable. The vtable might not be
close to the instance data and might be on another cache page.
(The required offset data is likely to be located on the cache page.)
• The last indirection to access the member is also very fast since it
is a relative address to the instance itself —which hopefully is
already be in the cache.
struct/class Layout: Overhead (con’t)
Using a virtual member function requires at least one additional
indirection
relative to a non-virtual function call:
One (1) indirection is needed to call the function if the function pointer is directly
stored in the object.
• Pro: Fast since function pointer is extremely likely to be on the same L1 cache
page as the instance.
• Con: RAM to store the list of function pointers in each instance plus the
constructor overhead to set such in each instance.
NOTE: Each indirect function pointer call is slower than a direct call.
struct/class Layout: Overhead (con’t)
Two (2) indirections are needed to call the function if the function pointer is stored in
the vtable.
• Pro: Only one vtable pointer in each instance is required: constructors need only
set one pointer.
• Con: An extra cache page and indirection:
• The vtable is not likely to be close to the instance data and will need to use an
additional cache page.
• All slots are extremely likely to be located on the vtable cache page, so the slot
(array) indirection will be negligible (only) for direct virtual function calls since
the compiler already knows the slot offset at compile time. (If indirect then add
another indirection.)
• The last indirection is the indirect function call itself.
struct/class Layout: Overhead (con’t)
Accessing a virtual member function through a virtual member base can
require three to four indirections and likely at least an additional cache
page access (for the vtable).
• Is your code also using pointers to everything as well? Hmmmm…
• There are some cases where the compiler can apply some optimizations —but,
in general, this overhead is very real.
If for each object o1 of type S there is an object o2 of type T such that for
all programs P defined in terms of T, the behaviour of P is unchanged
when o1 is substituted for o2, then S is a subtype of T.
When To Use And Not To Use Inheritance
(con’t)
Don’t use public inheritance when: [4, 5]
• non-public inheritance is sufficient
• membership, i.e., Has-A, is sufficient
• declaring as a member variable; encapsulation is nearly always sufficient
• the relationship is Is-Almost-A, e.g.,
• A Square cannot, in practice, inherit from a Rectangle since a
Rectangle’s equivalent of set_width() and set_height() members will
have adverse unacceptable side effects when applied to a Square.
• i.e., Square fails the LSP with respect to Rectangle.
• the relationship is Is-Implemented-In-Terms-Of since such is really a
Has-A
relationship.
When To Use And Not To Use Inheritance
(con’t)
Only use private inheritance to model Has-A relationships if and only if:
• one needs access to protected base class members, and/or,
• one needs to override a virtual function;
C++ permits pure virtual functions to be defined —although the function and
type are still considered pure and the function must be explicitly invoked in a
derived type if it is to be executed…
Pure virtual Functions (con’t)
w03/layout-egg1b.cxx
1 class position
2 { // abstract class due to pure virtual functions
3 public:
4 virtual ~position() { } // ensure proper destruction
5 virtual int getX() const = 0;
6 virtual int getY() const = 0;
7 virtual void setX(int x) = 0;
8 virtual void setY(int y) = 0;
9 virtual void move(int dx, int dy) = 0;
10 };
11
12 void position::move(int dx, int dy) {
13 setX(getX()+dx); setY(getY()+dy);
14 }
Pure virtual Functions (con’t)
w03/layout-egg1c.cxx
1 // euclidean does not override all pure virtual
2 // functions and so is still an abstract class...
3 class euclidean : public position
4 {
5 public:
6 void move(int dx, int dy) override
7 {
8 position::move(dx, dy);
9 }
10 };
NOTE: The above example is a bit contrived. It is also very rare to write definitions
for pure virtual functions. It works since the pure virtual requires a slot and the
definition causes the function pointer to be set on that slot.
struct and class There are no differences between
struct or class in C++
Rule of thumb: Use structs as one would in C and classes for OOP classes
and virtually all other user-defined objects.
Member Initialization Lists
All versions of C++ have member initialization lists.
C++ member initialization lists are used in constructors to invoke base
class constructors and to construct member variables.
If any base classes or members are omitted from the list, then they are implicitly
default constructed.
The member initialization list appears starting with a colon, “:”,
immediately after the close parenthesis, “)”, ending the constructor
argument list.
It ends with the opening brace, “{”, of the constructor definition’s body.
The allowed syntax is that of declaring comma-separated unnamed
variables to invoke constructors or and using the member name instead of a
type to invoke members’ constructors.
Member Initialization Lists (con’t)
w03/mem-init-list1.cxx
1 struct base {
2 base() : b_(12) { } // Construct b_ with 12.
3 int b_;
4 };
5 struct derived {
6 derived() :
7 base{}, d_(231) // Default construct base; d_ = 231
8 { }
9 int d_;
10 };
11 void func(derived const&) { }
12 derived d; // Default constructed
13 derived d2{}; // Default constructed
14 func(derived{}); // Unnamed & def. constructed.
15 // Next line is the "most vexing parse"...
16 derived d3(); // This is a function pointer declaration.
A reason member initialization lists are needed is because once the open
curly brace of the constructor code block is encountered all base classes and
all members have been successfully constructed. Thus, the list
allows the programmer to control the construction process of base classes and
members.
Your code’s ordering of construction should be as follows:
• base classes then
• member variables (in order of declared appearance in the class)
std::initializer_list
The list can be empty, or, it can be filled with literals/variables provided they are all of
the same type. No implicit conversions or narrowings will occur. [1, [dcl.init.list]]
NOTE: If a type has a single-argument constructor with an std::initializer_list
defined, then the std::initializer_list constructor will be preferred if the uniform
initialization brace syntax is used to construct the type.
std::initializer_list (con’t)
w03/init-list1.cxx
1 #include <initializer_list>
2 #include <iostream>
3 #include <vector>
4
5 // Invokes std::vector<int>(std::initializer_list<int>)...
6 std::vector<int> v{ 1, 5, 23, -123, 45, 34 56 };
7 template <typename T>
8 T sum(std::initializer_list<T> l)
9 {
10 T retval{};
11 for (auto const& element : l)
12 retval += element;
13 return retval;
14 }
15 // ...
16 std::cout << sum({1, 2, 3}) << '\n'; // Outputs: 6
std::initializer_list (con’t)
w03/init-list2.cxx
1 #include <initializer_list>
2 #include <list>
3
4 struct my_list
5 {
6 my_list(std::initializer_list<int> l) :
7 l_(l.begin(), l.end())
8 // i.e., Pass iterators to list<int> constructor
9 {
10 }
11
12 list<int> l_;
13 };
14
15 // Construct my_list with some ints...
16 my_list wow{ 1, -53, 3, 123, 45, 234 };
Resource Acquisition Is Initialization (RAII)
Resource Acquisition Is Initialization is an extremely important
design pattern in C++. It involves writing objects to acquire and
release resources as follows:
• In the constructor(s), obtain the required resources.
• In the destructor, always free all obtained resources over the lifetime of the
object.
• It is usually preferable to declare objects by value on the call stack to
ensure the destructor is automatically called.