0% found this document useful (0 votes)
6 views

Intro To C++ Object Model - Richard Powell - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

Intro To C++ Object Model - Richard Powell - CppCon 2015

Uploaded by

alan88w
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 115

Intro to the C++ Object Model

Richard Powell <[email protected]>


9/23/2015 - v1.4
Intro to my Intro

• Why this talk

• All code here-in is example code

• Production code should be code-reviewed, build with no


warnings, etc…
P r oTip!

make is your friend


$ cat intro.cpp
#include <iostream>
int main() { std::cout<<"hello world\n"; }

$ make intro
c++ intro.cpp -o intro

$ ./intro
hello world

• make’s auto deduction rules will deduce they should use $CXX to
convert cpp files to executables.
P r oTip!

Celebrate like it’s 2014

$ make CXXFLAGS="-std=c++14 -stdlib=libc++" intro


c++ -std=c++1y -stdlib=libc++ intro.cpp -o intro

$ ./intro
hello world

• ‘-std=c++14’ is the flag to use C++14 (or ‘-std=c++1y’ on legacy)

• ‘-stdlib=libc++’ will use libc++, the recommended library for C++14


Object-Oriented Programming
Object-Oriented Programming
• What is Object-Oriented Programming?
Object-Oriented Programming
• What is Object-Oriented Programming?

• “Object-oriented programming (OOP) is a programming paradigm


that represents the concept of "objects" that have data fields
(attributes that describe the object) and associated procedures
known as methods” - http://en.wikipedia.org/wiki/Object-
oriented_programming
Object-Oriented Programming
• What is Object-Oriented Programming?

• “Object-oriented programming (OOP) is a programming paradigm


that represents the concept of "objects" that have data fields
(attributes that describe the object) and associated procedures
known as methods” - http://en.wikipedia.org/wiki/Object-
oriented_programming

• “In this way, the data structure becomes an object that includes
both data and functions.” - http://www.webopedia.com/TERM/O/
object_oriented_programming_OOP.html
C++ Object model
C++ Object model
• “An object is a region of storage.” ISO N3690: § 1.8 [intro.object]
C++ Object model
• “An object is a region of storage.” ISO N3690: § 1.8 [intro.object]

• If you were to design a system that has inheritance and run-time


determined functionality (polymorphism), how would you do it?
C++ Object model
• “An object is a region of storage.” ISO N3690: § 1.8 [intro.object]

• If you were to design a system that has inheritance and run-time


determined functionality (polymorphism), how would you do it?

• Constraints:
C++ Object model
• “An object is a region of storage.” ISO N3690: § 1.8 [intro.object]

• If you were to design a system that has inheritance and run-time


determined functionality (polymorphism), how would you do it?

• Constraints:

• Compatiblity with ANSI-C


C++ Object model
• “An object is a region of storage.” ISO N3690: § 1.8 [intro.object]

• If you were to design a system that has inheritance and run-time


determined functionality (polymorphism), how would you do it?

• Constraints:

• Compatiblity with ANSI-C

• “You shouldn’t pay for what you don’t use”


quiz_c_size.c: $ make quiz_c_size
$ ./quiz_c_size
sizeof(float): 4

#include <stdio.h>

typedef struct
{
float real;
float imag; A sizeof(Complex): 4
} Complex;

int main()
{
printf("sizeof(float): %ld\n", sizeof(float));
printf("sizeof(Complex): %ld\n", sizeof(Complex));
B sizeof(Complex): 8

C sizeof(Complex): 16
quiz_c_size.c: $ make quiz_c_size
$ ./quiz_c_size
sizeof(float): 4

#include <stdio.h>

typedef struct
{
float real;
float imag; A sizeof(Complex): 4
} Complex;

int main()
{
printf("sizeof(float): %ld\n", sizeof(float));
printf("sizeof(Complex): %ld\n", sizeof(Complex));
B sizeof(Complex): 8

C sizeof(Complex): 16
quiz_c_offset.c: $ make quiz_c_offset
$ ./quiz_c_offset
address of c: 0x10080

#include <stdio.h>

typedef struct
{
float real; address of c.real: 0x10080
float imag;
A address of c.imag: 0x10084
} Complex;

int main()
{
Complex c; address of c.real: 0x10084
printf("address of c: %p\n", &c); address of c.imag: 0x10080
printf("address of c.real: %p\n", &c.real); B
printf("address of c.imag: %p\n", &c.imag);
}

address of c.real: 0x10084


C address of c.imag: 0x10088
quiz_c_offset.c: $ make quiz_c_offset
$ ./quiz_c_offset
address of c: 0x10080

#include <stdio.h>

typedef struct
{
float real; address of c.real: 0x10080
float imag;
A address of c.imag: 0x10084
} Complex;

int main()
{
Complex c; address of c.real: 0x10084
printf("address of c: %p\n", &c); address of c.imag: 0x10080
printf("address of c.real: %p\n", &c.real); B
printf("address of c.imag: %p\n", &c.imag);
}

address of c.real: 0x10084


C address of c.imag: 0x10088
quiz_c_offset.c:

#include <stdio.h>

typedef struct
{
float real;
float imag;
} Complex;

int main()
{
Complex c;
printf("address of c: %p\n", &c);
printf("address of c.real: %p\n", &c.real);
printf("address of c.imag: %p\n", &c.imag); Complex
float
} 0x10084
imag
float
real 0x10080
0x10080 c
quiz_size1.cpp: $ make quiz_size1
$ ./quiz_size1
sizeof(float): 4

#include <iostream>

struct Complex
{
float real;
float imag; A sizeof(Complex): 4
};

int main()
{
std::cout<<"sizeof(float): "<<sizeof(float)<<"\n";
std::cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
B sizeof(Complex): 8

C sizeof(Complex): 16
quiz_size1.cpp: $ make quiz_size1
$ ./quiz_size1
sizeof(float): 4

#include <iostream>

struct Complex
{
float real;
float imag; A sizeof(Complex): 4
};

int main()
{
std::cout<<"sizeof(float): "<<sizeof(float)<<"\n";
std::cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
B sizeof(Complex): 8

C sizeof(Complex): 16
quiz_offset1.cpp: $ make quiz_offset1
$ ./quiz_offset1
address of c: 0x10080

#include <iostream>
using std::cout;

struct Complex
{ address of c.real: 0x10080
float real;
float imag; A address of c.imag: 0x10084
};

int main()
{
address of c.real: 0x10084
Complex c;
cout<<"address of c: "<<&c<<"\n"; B address of c.imag: 0x10080
cout<<"address of c.real: "<<&c.real<<"\n";
cout<<"address of c.imag: "<<&c.imag<<"\n";
}

address of c.real: 0x10084


C address of c.imag: 0x10088
P r oTip!

#include <iostream>
using std::cout;

• The using directive controls importing of symbols from namespaces

• You *can* import all symbols from a namespace (i.e. “using


namespace std”), but “Good” programmers are selective and
surgical

• Only import what you need

• NEVER blindly use using to import symbols into a header file!


quiz_offset1.cpp: $ make quiz_offset1
$ ./quiz_offset1
address of c: 0x10080

#include <iostream>
using std::cout;

struct Complex
{ address of c.real: 0x10080
float real;
float imag; A address of c.imag: 0x10084
};

int main()
{
address of c.real: 0x10084
Complex c;
cout<<"address of c: "<<&c<<"\n"; B address of c.imag: 0x10080
cout<<"address of c.real: "<<&c.real<<"\n";
cout<<"address of c.imag: "<<&c.imag<<"\n";
}

address of c.real: 0x10084


C address of c.imag: 0x10088
quiz_offset1.cpp: $ make quiz_offset1
$ ./quiz_offset1
address of c: 0x10080

#include <iostream>
using std::cout;

struct Complex
{ address of c.real: 0x10080
float real;
float imag; A address of c.imag: 0x10084
};

int main()
{
address of c.real: 0x10084
Complex c;
cout<<"address of c: "<<&c<<"\n"; B address of c.imag: 0x10080
cout<<"address of c.real: "<<&c.real<<"\n";
cout<<"address of c.imag: "<<&c.imag<<"\n";
}

address of c.real: 0x10084


C address of c.imag: 0x10088
POD

• C++ rule is that member variables declared later in a structure must


be at a higher address

• These are called POD objects (Plain Old Data)

• Objects of POD types are fully compatible with the C programming


language.
P r oTip!

is_pod
demo_is_pod.cpp:

#include <iostream>
#include <type_traits> $ make demo_is_pod
using std::cout; $ ./demo_is_pod
is Complex a POD? yes
struct Complex
{
float real;
float imag;
};

int main()
{
cout<<"is Complex a POD? "<<
(std::is_pod<Complex>() ? "yes" : "no")<<"\n";
}
quiz_size_class.cpp: $ make quiz_size_class
$ ./quiz_size_class
sizeof(float): 4

#include <iostream>
using std::cout;

class Complex
{
float real;
float imag;
A sizeof(Complex): 4

};

int main()
{
cout<<"sizeof(float): "<<sizeof(float)<<"\n"; B sizeof(Complex): 8
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
}

C sizeof(Complex): 16
quiz_size_class.cpp: $ make quiz_size_class
$ ./quiz_size_class
sizeof(float): 4

#include <iostream>
using std::cout;

class Complex
{
float real;
float imag;
A sizeof(Complex): 4

};

int main()
{
cout<<"sizeof(float): "<<sizeof(float)<<"\n"; B sizeof(Complex): 8
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
}

C sizeof(Complex): 16
• In C++, the only difference between a struct and a class is the
default accessor value

struct Complex
class Complex
{
{

};
float real;
float imag; == private:
float real;
float imag;
};
quiz_derived.cpp: $ make quiz_derived
$ ./quiz_derived
sizeof(float): 4

#include <iostream>
using std::cout;

struct Complex
{ sizeof(Complex): 8
float real; A sizeof(Derived): 8
float imag;
};

struct Derived : public Complex


{
float angle; sizeof(Complex): 8
}; B sizeof(Derived): 12
int main()
{
cout<<"sizeof(float): "<<sizeof(float)<<"\n";
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
cout<<"sizeof(Derived): "<<sizeof(Derived)<<"\n";
sizeof(Complex): 8
} C sizeof(Derived): 16
quiz_derived.cpp: $ make quiz_derived
$ ./quiz_derived
sizeof(float): 4

#include <iostream>
using std::cout;

struct Complex
{ sizeof(Complex): 8
float real; A sizeof(Derived): 8
float imag;
};

struct Derived : public Complex


{
float angle; sizeof(Complex): 8
}; B sizeof(Derived): 12
int main()
{
cout<<"sizeof(float): "<<sizeof(float)<<"\n";
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
cout<<"sizeof(Derived): "<<sizeof(Derived)<<"\n";
sizeof(Complex): 8
} C sizeof(Derived): 16
quiz_offset2.cpp: $ make quiz_offset2
$ ./quiz_offset2
address of d: 0x10080

#include <iostream>
using std::cout;
address of d.real: 0x10080
struct Complex
{ address of d.imag: 0x10084
float real; A address of d.angle: 0x10088
float imag;
};

struct Derived : public Complex


{ address of d.real: 0x10084
float angle; address of d.imag: 0x10088
};
B
address of d.angle: 0x10080
int main()
{
Derived d;
cout<<"address of d: "<<(&d)<<"\n";
cout<<"address of d.real: "<<(&d.real)<<"\n"; address of d.real: 0x10084
cout<<"address of d.imag: "<<(&d.imag)<<"\n"; C address of d.imag: 0x10088
cout<<"address of d.angle: "<<(&d.angle)<<"\n"; address of d.angle: 0x1008c
}
quiz_offset2.cpp: $ make quiz_offset2
$ ./quiz_offset2
address of d: 0x10080

#include <iostream>
using std::cout;
address of d.real: 0x10080
struct Complex
{ address of d.imag: 0x10084
float real; A address of d.angle: 0x10088
float imag;
};

struct Derived : public Complex


{ address of d.real: 0x10084
float angle; address of d.imag: 0x10088
};
B
address of d.angle: 0x10080
int main()
{
Derived d;
cout<<"address of d: "<<(&d)<<"\n";
cout<<"address of d.real: "<<(&d.real)<<"\n"; address of d.real: 0x10084
cout<<"address of d.imag: "<<(&d.imag)<<"\n"; C address of d.imag: 0x10088
cout<<"address of d.angle: "<<(&d.angle)<<"\n"; address of d.angle: 0x1008c
}
• Inheritance works by “extending” the object

• Think of inheritance as essentially “stacking” subclasses on top of


base classes

struct Complex
{ struct Derived
float real; {
float imag; struct {
};

struct Derived : public Complex


“==“ };
float real;
float imag;

{ float angle;
float angle; };
};
Compiler’s view of the stack

#include <iostream>

struct Complex
{
float real;
float imag;
};

struct Derived : public Complex


{
float angle;
};

int main()
{
Derived d;
Complex& c = d;
}
Compiler’s view of the stack
What d looks like:
#include <iostream>

struct Complex
{
float real;
float imag;
};

struct Derived : public Complex


{
float angle; Derived
}; float
0x10088
angle
int main()
Complex
{ float
Derived d; 0x10084
Complex& c = d;
imag
} float
real 0x10080
0x10080 d
Compiler’s view of the stack
What c looks like:
#include <iostream>

struct Complex
{
float real;
float imag;
};

struct Derived : public Complex


{
float angle; Derived
}; float
0x100
int main()
Complex
{ float
Derived d; 0x10084
Complex& c = d;
imag
} float
real 0x10080
0x10080 c
quiz_mem_func.cpp: $ make quiz_mem_func
$ ./quiz_mem_func
sizeof(float): 4

#include <iostream>
#include <cmath>
using std::cout;

struct Complex
{
float Abs() const { return std::hypot(real, imag); }
float real;
A sizeof(Complex): 4

float imag;
};

int main()
{ B sizeof(Complex): 8
cout<<"sizeof(float): "<<sizeof(float)<<"\n";
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
}

C sizeof(Complex): 16
quiz_mem_func.cpp: $ make quiz_mem_func
$ ./quiz_mem_func
sizeof(float): 4

#include <iostream>
#include <cmath>
using std::cout;

struct Complex
{
float Abs() const { return std::hypot(real, imag); }
float real;
A sizeof(Complex): 4

float imag;
};

int main()
{ B sizeof(Complex): 8
cout<<"sizeof(float): "<<sizeof(float)<<"\n";
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n";
}

C sizeof(Complex): 16
Conceptually how member function are formed
struct Complex
{
float Abs() const;
float real;
float imag;
};

float Complex::Abs() const


{
return std::hypot(real, imag);
}

Complex c;
auto v = c.Abs();
float Complex::Abs() const
{
return std::hypot(real, imag);
}

Complex c;
auto v = c.Abs();
A pointer the this object is added as the first argument

float Complex::Abs() const


{
return std::hypot(real, imag);
}

Complex c;
auto v = c.Abs();
A pointer the this object is added as the first argument

float Complex::Abs(Complex * this) const


{
return std::hypot(real, imag);
}

Complex c;
auto v = c.Abs();
The CV-qualifiers are applied to the this object

float Complex::Abs(Complex * this) const


{
return std::hypot(real, imag);
}

Complex c;
auto v = c.Abs();
The CV-qualifiers are applied to the this object

float Complex::Abs(Complex const * this)


{
return std::hypot(real, imag);
}

Complex c;
auto v = c.Abs();
The CV-qualifiers are applied to the this object

float Complex::Abs(Complex const * this)


{
return std::hypot(real, imag);
}

For function invocation, the object is moved to first argument

Complex c;
auto v = c.Abs();
The CV-qualifiers are applied to the this object

float Complex::Abs(Complex const * this)


{
return std::hypot(real, imag);
}

For function invocation, the object is moved to first argument

Complex c;
auto v = Abs(&c);
All member variables are prefixed to use the this object

float Complex::Abs(Complex const * this)


{
return std::hypot(real, imag);
}

Complex c;
auto v = Abs(&c);
All member variables are prefixed to use the this object

float Complex::Abs(Complex const * this)


{
return std::hypot(this->real, this->imag);
}

Complex c;
auto v = Abs(&c);
The compiler translates the function to a free function with
a implementation defined “name mangled” version

float Complex::Abs(Complex const * this)


{
return std::hypot(this->real, this->imag);
}

Complex c;
auto v = Abs(&c);
The compiler translates the function to a free function with
a implementation defined “name mangled” version

float __ZNK7Complex3AbsEv(Complex const * this)


{
return std::hypot(this->real, this->imag);
}

For function invocation, the function call is also translated

Complex c;
auto v = Abs(&c);
The compiler translates the function to a free function with
a implementation defined “name mangled” version

float __ZNK7Complex3AbsEv(Complex const * this)


{
return std::hypot(this->real, this->imag);
}

For function invocation, the function call is also translated

Complex c;
auto v = __ZNK7Complex3AbsEv(&c);
float Complex::Abs() const
{
Before return std::hypot(real, imag);
}

float __ZNK7Complex3AbsEv(Complex const * this)


{
After return std::hypot(this->real, this->imag);
}
P r oTip!

c++filt to translate

$ c++filt __ZNK7Complex3AbsEv
Complex::Abs() const

• Use c++filt to translate name mangled functions to original


names
#include <math.h>
#include <cmath>
typedef struct
struct Complex
{
{
float real;
float Abs() const
{
return std::hypot(real, imag);
“==“ float imag;
} Complex;
}
float __ZNK7Complex3AbsEv(Complex const* this)
float real;
{
float imag;
return hypot(this->real, this->imag);
};
}
#include <iostream>
using std::cout;

struct Erdos
{
void whoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


{
void whoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};
quiz_virtual1.cpp: $ make quiz_virtual1
$ ./quiz_virtual1
I am Erdos
I really am Erdos

struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
}; I am Erdos
struct Fermat : public Erdos
A I really am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{ I am Erdos
Erdos e; B I really am Fermat
e.whoAmI();
e.whoAmIReally();

Fermat f;
f.whoAmI();
f.whoAmIReally(); I am Fermat
} C I really am Fermat
quiz_virtual1.cpp: $ make quiz_virtual1
$ ./quiz_virtual1
I am Erdos
I really am Erdos

struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
}; I am Erdos
struct Fermat : public Erdos
A I really am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{ I am Erdos
Erdos e; B I really am Fermat
e.whoAmI();
e.whoAmIReally();

Fermat f;
f.whoAmI();
f.whoAmIReally(); I am Fermat
} C I really am Fermat
quiz_virtual2.cpp: $ make quiz_virtual2
$ ./quiz_virtual2
I am Fermat
I really am Fermat

struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
}; I am Erdos
struct Fermat : public Erdos
A I really am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{ I am Erdos
Fermat f; B I really am Fermat
f.whoAmI();
f.whoAmIReally();

Erdos& e = f;
e.whoAmI();
e.whoAmIReally(); I am Fermat
} C I really am Fermat
quiz_virtual2.cpp: $ make quiz_virtual2
$ ./quiz_virtual2
I am Fermat
I really am Fermat

struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
}; I am Erdos
struct Fermat : public Erdos
A I really am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{ I am Erdos
Fermat f; B I really am Fermat
f.whoAmI();
f.whoAmIReally();

Erdos& e = f;
e.whoAmI();
e.whoAmIReally(); I am Fermat
} C I really am Fermat
#include <iostream>
using std::cout;

struct Erdos
{
void whoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


{
void whoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

• Non-virtual member functions bind statically. Function resolution


occurs at compile time.

• Virtual member functions bind dynamically. Function resolution


occurs when the object is created.
quiz_virtual2.cpp: $ make quiz_virtual2
$ ./quiz_virtual2
I am Fermat
I really am Fermat

struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
}; I am Erdos
struct Fermat : public Erdos
A I really am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{ I am Erdos
Fermat f; B I really am Fermat
f.whoAmI(); non-virtual functions resolved statically
f.whoAmIReally();

Erdos& e = f;
e.whoAmI();
e.whoAmIReally(); I am Fermat
} C I really am Fermat
quiz_virtual2.cpp: $ make quiz_virtual2
$ ./quiz_virtual2
I am Fermat
I really am Fermat

struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
}; I am Erdos
struct Fermat : public Erdos
A I really am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{ I am Erdos
Fermat f; B I really am Fermat
f.whoAmI(); Virtual calls determined by object creation.
f.whoAmIReally(); Original object created as Fermat.
Fermat function is called.
Erdos& e = f;
e.whoAmI();
e.whoAmIReally(); I am Fermat
} C I really am Fermat
quiz_virtual3.cpp: $ make quiz_virtual3
$ ./quiz_virtual3
I am Erdos
I really am Erdos

struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
}; I am Erdos
struct Fermat : public Erdos A I really am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{ I am Erdos
Erdos * e1 = new Erdos; B I really am Fermat
e1->whoAmI();
e1->whoAmIReally();

Erdos * e2 = new Fermat;


e2->whoAmI();
e2->whoAmIReally(); I am Fermat
} C I really am Fermat
int main()
{
Erdos* e = new Erdos;
e->whoAmI();
e->whoAmIReally();
}
int main()
{
Erdos* e = new Erdos;
e->whoAmI();
e->whoAmIReally();
}
P r oTip!

int main()
{
Erdos* e = new Erdos;
e->whoAmI();
e->whoAmIReally();
}

int main()
{
using std::unique_ptr;
using std::make_unique;

unique_ptr<Erdos> e = make_unique<Erdos>();
e->whoAmI();
e->whoAmIReally();
}
P r oTip!

int main()
{
• unique_ptr is a C++ Smart
Erdos* e = new Erdos;
e->whoAmI(); Pointer
e->whoAmIReally();
}
• make_unique: convenience
function to make unique_ptrs

• manages raw resources


int main()
{
using std::unique_ptr;
using std::make_unique;
• ALWAYS use smart pointers
unique_ptr<Erdos> e = make_unique<Erdos>();
e->whoAmI(); • NEVER write new in your code
e->whoAmIReally();
}
quiz_virtual3.cpp: $ make quiz_virtual3
$ ./quiz_virtual3
I am Erdos
I really am Erdos
struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


I am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
A I really am Erdos
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
using std::unique_ptr; I am Erdos
using std::make_unique; B I really am Fermat
unique_ptr<Erdos> e1 = make_unique<Erdos>();
e1->whoAmI();
e1->whoAmIReally();

unique_ptr<Erdos> e2 = make_unique<Fermat>();
e2->whoAmI(); I am Fermat
e2->whoAmIReally();
C I really am Fermat
}
quiz_virtual3.cpp: $ make quiz_virtual3
$ ./quiz_virtual3
I am Erdos
I really am Erdos
struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


I am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
A I really am Erdos
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
using std::unique_ptr; I am Erdos
using std::make_unique; B I really am Fermat
unique_ptr<Erdos> e1 = make_unique<Erdos>();
e1->whoAmI();
e1->whoAmIReally();

unique_ptr<Erdos> e2 = make_unique<Fermat>();
e2->whoAmI(); I am Fermat
e2->whoAmIReally();
C I really am Fermat
}
quiz_virtual4.cpp: $ make quiz_virtual4
$ ./quiz_virtual4
I am Erdos
I really am Erdos
struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


I am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
A I really am Erdos
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
using std::unique_ptr; I am Erdos
using std::make_unique; B I really am Fermat
unique_ptr<Erdos> e = make_unique<Erdos>();
e->whoAmI();
e->whoAmIReally();

unique_ptr<Fermat> f = make_unique<Fermat>();
f->whoAmI(); I am Fermat
f->whoAmIReally();
C I really am Fermat
}
quiz_virtual4.cpp: $ make quiz_virtual4
$ ./quiz_virtual4
I am Erdos
I really am Erdos
struct Erdos
{
voidwhoAmI() { cout<<"I am Erdos\n"; }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


I am Erdos
{
voidwhoAmI() { cout<<"I am Fermat\n"; }
A I really am Erdos
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
using std::unique_ptr; I am Erdos
using std::make_unique; B I really am Fermat
unique_ptr<Erdos> e = make_unique<Erdos>();
e->whoAmI();
e->whoAmIReally();

unique_ptr<Fermat> f = make_unique<Fermat>();
f->whoAmI(); I am Fermat
f->whoAmIReally();
C I really am Fermat
}
$ make quiz_size3
quiz_size3.cpp: $ ./quiz_size3
sizeof(float): 4
sizeof(void*): 8

struct Complex
{
virtual ~Complex() = default;
virtual float Abs() { return std::hypot(real, imag); }
float real;
float imag; A sizeof(Complex): 8
};
sizeof(Derived): 12

struct Derived : public Complex


{
virtual ~Derived() = default;
virtual float Abs() { return std::hypot(std::hypot(real, imag), angle); }
float angle; B sizeof(Complex): 16
}; sizeof(Derived): 24

int main()
{
cout<<"sizeof(float): "<<sizeof(float)<<"\n";
cout<<"sizeof(void*): "<<sizeof(void*)<<"\n";
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n"; C sizeof(Complex): 16
cout<<"sizeof(Derived): "<<sizeof(Derived)<<"\n"; sizeof(Derived): 32
}
$ make quiz_size3
quiz_size3.cpp: $ ./quiz_size3
sizeof(float): 4
sizeof(void*): 8

struct Complex
{
virtual ~Complex() = default;
virtual float Abs() { return std::hypot(real, imag); }
float real;
float imag; A sizeof(Complex): 8
};
sizeof(Derived): 12

struct Derived : public Complex


{
virtual ~Derived() = default;
virtual float Abs() { return std::hypot(std::hypot(real, imag), angle); }
float angle; B sizeof(Complex): 16
}; sizeof(Derived): 24

int main()
{
cout<<"sizeof(float): "<<sizeof(float)<<"\n";
cout<<"sizeof(void*): "<<sizeof(void*)<<"\n";
cout<<"sizeof(Complex): "<<sizeof(Complex)<<"\n"; C sizeof(Complex): 16
cout<<"sizeof(Derived): "<<sizeof(Derived)<<"\n"; sizeof(Derived): 32
}
quiz_offset3.cpp: $ make quiz_offset3
$ ./quiz_offset3
address of d: 0x10080

struct Complex
{
virtual ~Complex() = default;
virtual float Abs() { return std::hypot(real, imag); } address of d.real: 0x10080
float real; address of d.imag: 0x10084
float imag; A address of d.angle: 0x10088
};

struct Derived : public Complex


{
virtual ~Derived() = default;
address of d.real: 0x10088
virtual float Abs() { return std::hypot(std::hypot(real, imag), angle); }
address of d.imag: 0x1008c
};
float angle; B address of d.angle: 0x10098

int main()
{
Derived d;
cout<<"address of d: "<<(&d)<<"\n"; address of d.real: 0x10088
cout<<"address of d.real: "<<(&d.real)<<"\n";
cout<<"address of d.imag: "<<(&d.imag)<<"\n"; C address of d.imag: 0x1008c
cout<<"address of d.angle: "<<(&d.angle)<<"\n"; address of d.angle: 0x10090
}
quiz_offset3.cpp: $ make quiz_offset3
$ ./quiz_offset3
address of d: 0x10080

struct Complex
{
virtual ~Complex() = default;
virtual float Abs() { return std::hypot(real, imag); } address of d.real: 0x10080
float real; address of d.imag: 0x10084
float imag; A address of d.angle: 0x10088
};

struct Derived : public Complex


{
virtual ~Derived() = default;
address of d.real: 0x10088
virtual float Abs() { return std::hypot(std::hypot(real, imag), angle); }
address of d.imag: 0x1008c
};
float angle; B address of d.angle: 0x10098

int main()
{
Derived d;
cout<<"address of d: "<<(&d)<<"\n"; address of d.real: 0x10088
cout<<"address of d.real: "<<(&d.real)<<"\n";
cout<<"address of d.imag: "<<(&d.imag)<<"\n"; C address of d.imag: 0x1008c
cout<<"address of d.angle: "<<(&d.angle)<<"\n"; address of d.angle: 0x10090
}
Complex float
How member function work with virtual
imag
float
struct Complex {
virtual ~Complex() = default;
real
virtual float Abs();
float real;
float imag;
};

float Complex::Abs()
{
return std::hypot(real, imag);
}

Complex original_c;
Complex& c = original_c;
float ans = c.Abs();
Complex float
Member functions are generated as normal
imag
imag
float
struct Complex {
virtual ~Complex() = default;
real
real
virtual float Abs();
float real;
float imag;
};

float __ZNK7Complex3AbsEv(Complex const * this)


{
return std::hypot(this->real, this->imag);
}

Complex original_c;
Complex& c = original_c;
float ans = c.Abs();
Complex float
A table of all virtual functions is generated (the vtable)
imag
float
struct Complex {
virtual ~Complex() = default;
real
virtual float Abs();
float real;
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

Complex original_c;
Complex& c = original_c;
float ans = c.Abs();
Complex float
The compiler silently inserts a point to the table
imag
float
struct Complex {
$$tbl$$ * vtable;
real
virtual ~Complex() = default; $$tbl$$*
virtual float Abs();
float real; vtable
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

Complex original_c;
Complex& c = original_c;
float ans = c.Abs();
Complex float

imag
float
struct Complex {
$$tbl$$ * vtable;
real
virtual ~Complex() = default; $$tbl$$*
virtual float Abs();
float real; vtable
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

Compiler translates function call into


an indirect table lookup and call
Complex original_c;
Complex& c = original_c;
float ans = c.Abs();
Complex float

imag
float
struct Complex {
$$tbl$$ * vtable;
real
virtual ~Complex() = default; $$tbl$$*
virtual float Abs();
float real; vtable
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

Compiler translates function call into


an indirect table lookup and call
Complex original_c;
Complex& c = original_c;
float ans = c.vtable[???]();
Complex float

imag
imag
float
struct Complex {
$$tbl$$ * vtable;
real
real
virtual ~Complex() = default; $$tbl$$*
virtual float Abs();
float real;
vtable
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

The function offset is determined

Complex original_c;
Complex& c = original_c;
float ans = c.vtable[???]();
Complex float

imag
imag
float
struct Complex {
$$tbl$$ * vtable;
real
real
virtual ~Complex() = default; $$tbl$$*
virtual float Abs();
float real;
vtable
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

The function offset is determined

Complex original_c;
Complex& c = original_c;
float ans = c.vtable[1/*OffsetOf_Abs*/]();
Complex float

imag
float
struct Complex {
$$tbl$$ * vtable;
real
virtual ~Complex() = default; $$tbl$$*
virtual float Abs();
float real;
vtable
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

And we pass a pointer to the this object

Complex original_c;
Complex& c = original_c;
float ans = c.vtable[1/*OffsetOf_Abs*/]();
Complex float

imag
float
struct Complex {
$$tbl$$ * vtable;
real
virtual ~Complex() = default; $$tbl$$*
virtual float Abs();
float real;
vtable
float imag;
};

ComplexTbl
dtor*
float __ZNK7Complex3AbsEv(Complex const * this)
{
return std::hypot(this->real, this->imag);
}
float(*)

And we pass a pointer to the this object

Complex original_c;
Complex& c = original_c;
float ans = c.vtable[1/*OffsetOf_Abs*/](&c);
_const _text
struct Complex
{
virtual ~Complex() {}
virtual float Abs() { ;;; }
float real;
float imag;
};

int main()
{
Complex d;
}
_const _text
struct Complex
{
virtual ~Complex() {}
virtual float Abs() { ;;; }
float real; Complex::~Complex() {}
float imag;
};
float Complex::Abs() { ;;

int main()
{
Complex d;
}
_const _text
struct Complex
{
virtual ~Complex() {} ComplexTbl
virtual float Abs() { ;;; } dtor*
float real; Complex::~Complex() {}
float imag;
};
float(*)
float Complex::Abs() { ;;

int main()
{
Complex d;
}
_const _text
struct Complex
{
virtual ~Complex() {} ComplexTbl
virtual float Abs() { ;;; } dtor*
float real; Complex::~Complex() {}
float imag;
};
float(*)
float Complex::Abs() { ;;

Complex float

imag
float
real
int main() $$tbl$$*
{
Complex d;
} vtable
YMMV
• From Working Draft N4431, Clause 9.2, Note 13:

• Nonstatic data members of a (non-union) class with the same access


control (Clause 11) are allocated so that later members have higher
addresses within a class object. The order of allocation of non-static
data members with different access control is unspecified (Clause 11).
Implementation alignment requirements might cause two adjacent
members not to be allocated immediately after each other; so might
requirements for space for managing virtual functions (10.3) and virtual
base classes (10.1).

• Layout of Non-POD is implementation defined


P r oTip!

When in doubt…
• Read the standard:

• http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4431.pdf
_const _text
struct Complex
{
virtual ~Complex() {}
virtual float Abs() { ;;; }
float real;
float imag;
};

struct Derived : public Complex


{
virtual ~Derived() {}
virtual float Abs() { ;;; }
float angle;
};

int main()
{
Complex c;
}
_const _text
struct Complex
{
virtual ~Complex() {}
virtual float Abs() { ;;; }
float real; Complex::~Complex() {}
float imag;
};
float Complex::Abs() { ;;
struct Derived : public Complex
{
virtual ~Derived() {}
virtual float Abs() { ;;; }
float angle;
};

int main()
{
Complex c; Derived::~Derived() {}
}

float Derived::Abs() { ;;
_const _text
struct Complex
{
virtual ~Complex() {} ComplexTbl
virtual float Abs() { ;;; } dtor*
float real; Complex::~Complex() {}
float imag;
};
float(*)
float Complex::Abs() { ;;
struct Derived : public Complex
{
virtual ~Derived() {}
virtual float Abs() { ;;; }
float angle;
};

int main() DerivedTbl


{ dtor*
Complex c; Derived::~Derived() {}
}

float Derived::Abs() { ;;
float(*)
_const _text
struct Complex
{
virtual ~Complex() {} ComplexTbl
virtual float Abs() { ;;; } dtor*
float real; Complex::~Complex() {}
float imag;
};
float(*)
float Complex::Abs() { ;;
struct Derived : public Complex
{ Complex float
virtual ~Derived() {}
virtual float Abs() { ;;; } imag
float angle;
float
};
real
int main() $$tbl$$* DerivedTbl
{ dtor*
Complex c; Derived::~Derived() {}
} vtable
float Derived::Abs() { ;;
float(*)
_const _text
struct Complex
{
virtual ~Complex() {} ComplexTbl
virtual float Abs() { ;;; } dtor*
float real; Derived
Complex::~Complex() {}
float imag;
};
float
angle float(*)
float Complex::Abs() { ;;
struct Derived : public Complex
{ Complex float
virtual ~Derived() {}
virtual float Abs() { ;;; } imag
float angle;
float
};
real
int main() $$tbl$$* DerivedTbl
{ dtor*
Derived c; Derived::~Derived() {}
} vtable
float Derived::Abs() { ;;
float(*)
_const _text
struct Complex
{
virtual ~Complex() {} ComplexTbl
virtual float Abs() { ;;; } dtor*
float real; Derived
Complex::~Complex() {}
float imag;
};
float
angle float(*)
float Complex::Abs() { ;;
struct Derived : public Complex
{ Complex float
virtual ~Derived() {}
virtual float Abs() { ;;; } imag
float angle;
float
};
real
int main() $$tbl$$* DerivedTbl
{ dtor*
Derived c; Derived::~Derived() {}
} vtable
float Derived::Abs() { ;;
float(*)

Where is the code that sets the pointer?


C++ Constructors

• Virtual functions work by having pointers to functions (v-tables)


initialized to the desired functions at run-time

• The job of the constructor


struct Complex {
Complex();
virtual ~Complex() = default;
virtual float Abs();
float real;
float imag;
};

Complex::Complex()
{
}
struct Complex {
$$tbl$$ * vtable;
Complex();
virtual ~Complex() = default;
virtual float Abs();
float real;
float imag;
};

Complex::Complex()
{
}
struct Complex {
$$tbl$$ * vtable;
Complex();
virtual ~Complex() = default;
virtual float Abs();
float real;
float imag;
};

Complex::Complex()
{
vtable = ComplexTbl;
}
struct Derived : public Complex {
Derived();
virtual ~Derived() = default;
virtual float Abs();
float angle;
};

Derived::Derived()
{
}
struct Derived : public Complex {
Derived();
virtual ~Derived() = default;
virtual float Abs();
float angle;
};

Derived::Derived()
{
}
struct Derived : public Complex {
Derived();
virtual ~Derived() = default;
virtual float Abs();
float angle;
};

Derived::Derived()
{
this->Complex::Complex();
vtable = DerivedTbl;
}
struct Derived : public Complex {
Derived();
virtual ~Derived() = default;
virtual float Abs();
float angle;
};

Derived::Derived()
{
this->Complex::Complex();
Complex::Complex()
vtable = DerivedTbl;
} {
vtable = ComplexTbl;
}
quiz_ctor.cpp: $ make quiz_ctor
$ ./quiz_ctor
I really am Erdos

#include <iostream>
using std::cout;
I really am Erdos
struct Erdos A
{
Erdos() { whoAmIReally(); }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


{ B I really am Fermat
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
Erdos e;
Fermat f;
}
quiz_ctor.cpp: $ make quiz_ctor
$ ./quiz_ctor
I really am Erdos

#include <iostream>
using std::cout;
I really am Erdos
struct Erdos A
{
Erdos() { whoAmIReally(); }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


{ B I really am Fermat
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
Erdos e;
Fermat f;
}
quiz_ctor.cpp: $ make quiz_ctor
$ ./quiz_ctor
I really am Erdos

#include <iostream>
using std::cout;
I really am Erdos
struct Erdos A
{
Erdos() { whoAmIReally(); }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


{ B I really am Fermat
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
Erdos e; Fermat::Fermat()
Fermat f;{
this->Erdos::Erdos();
} vtable = FermatTbl;
}
quiz_ctor.cpp: $ make quiz_ctor
$ ./quiz_ctor
I really am Erdos

#include <iostream>
using std::cout;
I really am Erdos
struct Erdos A
{
Erdos() { whoAmIReally(); }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


{ B I really am Fermat
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
Erdos e; Fermat::Fermat()
Fermat f;{ Erdos::Erdos()
this->Erdos::Erdos();
{
} vtable = FermatTbl;
vtable = ErdosTbl;
} whoAmIReally();
}
quiz_ctor.cpp: $ make quiz_ctor
$ ./quiz_ctor
I really am Erdos

#include <iostream>
using std::cout;
I really am Erdos
struct Erdos A
{
Erdos() { whoAmIReally(); }
virtual void whoAmIReally() { cout<<"I really am Erdos\n"; }
};

struct Fermat : public Erdos


{ B I really am Fermat
virtual void whoAmIReally() { cout<<"I really am Fermat\n"; }
};

int main()
{
Erdos e; Fermat::Fermat()
Fermat f;{ Erdos::Erdos()
this->Erdos::Erdos();
NEVER call virtual functions
} {
}
vtable = FermatTbl;
vtable = ErdosTbl;
whoAmIReally();
in a constructor!!!
}
C++ Object model
• C++ object model is the way objects exist in memory

• Runtime polymorphism accomplished with vtables

• Can dramatically increase the size of small objects

• Important to keep in mind, affects debugging and optimizing.

• Do not call virtual functions in an object’s constructor.


References
• http://en.wikipedia.org/wiki/Object-oriented_programming

• http://www.webopedia.com/TERM/O/
object_oriented_programming_OOP.html

• Lippman, Stanley B., “Inside the C++ Object Model Paperback”,


Addison-Wesley, 1996

• http://cppreference.com
Questions?

You might also like