top of page
Writer's pictureSunil Kumar Yadav

Understanding C++ Object Model

Updated: Sep 21

Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). OOP involves features like encapsulation, data hiding, abstraction, polymorphism, modularity, etc which makes it a very popular programming paradigm.



C++ is one of the most popular languages which is being used in various domains and industries. It is being used in safety-critical embedded devices to high-performance high-speed financial trading systems. OOP revolves around key concept call objects and as a software engineer knowing how C++ implements object model help us to write better code. In this article, we will try to understand how the C++ object model is implemented with the help of a simple example.


Let's take an example of a simple C structure Point3D containing x, y, z coordinates, and another function Point3D_print() to print the respective values.

//example1.c
struct Point3D
{
	float x;
	float y;
	float z;
};

void Point3D_print( const struct Point3D *pd )
{
	printf("( %g, %g, %g )", pd->x, pd->y, pd->z );
}

In C, data abstraction and the operations that perform on it are declared separately i.e. there is no language-supported relationship between data and functions as can be seen with the above example. The above program does not require any overhead as a block of memory will hold Point3D and the function will directly operate on the memory of the Point3D object.


Let's try to do the same using C++. We can implement Point3D as independent Abstract Data Type (ADT) by declaring a class with x, y, z coordinates as private members and associated methods to read and write values of these private data members. To print Poin3D we can overload the out stream operator.


// example2.cpp
class Point3D
{
public:
	Point3D( float x = 0.0, float y = 0.0, float z = 0.0 ) 
         : _x( x ), _y( y ), _z( z ) 
	{
	}
	
	float x() { return _x; }
	float y() { return _y; }
	float z() { return _z; }
	void x( float val ) { _x = val; }
	void y( float val ) { _y = val; }
	void z( float val ) { _z = val; }
private:
	float _x;
	float _y;
	float _z;
};

// overloading output stream operator to print Point3D
inline ostream& operator<<( ostream &os, const Point3D &pt )
{
	os << "( " << pt.x() << ", " << pt.y() << ", " << pt.z() << " )";
};

Comparing the implementation of the above two examples it's clear these are not just two different programming styles but two different approaches to solving the problem.


As a programmer, we might think example 2's Point3D layout cost for supporting encapsulation would be high but in reality, there is no additional cost involved that will impact the performance. The three co-ordinate data members are directly contained within-class objects as in the case of the C struct. Member functions/methods are not reflected inside the Point3D object. For each non-inline member function, one copy is generated and for the inline function, they would be generated once per module(per translation unit). Due to this, there is no space or runtime penalty in supporting the encapsulation. Space and runtime overheads are added only with regards to the virtual function mechanism to support the runtime binding and virtual base class in the inheritance hierarchy.


For example, member function point::x() would be internally transformed to remove space and runtime overhead.


float Point3D::x() 
{ 
	return _x; 
}
	
// Pseudo code: Transformation applied by C++ Compiler 
float __Point3D__x(Point3D d)   // name mangling
{
	return d._x;
}

C++ Object Model


C++ has two flavors of data members, 1. static member data and 2. nonstatic data member, and three flavors of the member function 1. static member function, 2. nonstatic member function, and 3. virtual member function. Let's try to understand the C++ objects model with the help of a simpler one-dimensional Point.


// example3.cpp
class Point
{
public:
	Point( float xval );
	virtual ~Point();
	float x() const;
	static int PointCount();

protected:
	virtual ostream& print( ostream &os ) const;
	float _x;
	static float int _point_count;
};

As we can see in our Point class we have static data member and static member function along with virtual destructor and virtual print member function. Apart from this Point has one float variable and member function. C++ object model is optimized for space and access time. Non-static data members are allocated directly inside the class object. Static data members are stored outside the individual class object. As far as static and non-static member functions are concerned, both types of member functions are hoisted outside the class object.


Image 1: C++ Object Model

In order to support virtual function, C++ generates a table with pointers to virtual function and this is known as a virtual table. A pointer to the virtual table is inserted in each object and it's commonly known as vptr. The setting and resetting of vptr are handled within code generated in the constructor and destructor of the class. The type_info object associated with each class in support of runtime type identification (RTTI) is also addressed within the virtual table, usually within the table's first slot. The above image 1 demonstrates the C++ object model for the Point class of example3.cpp.


Inheritance and Object Model


C++ supports both single inheritance and multiple inheritances. In the case of virtual inheritance, the derived object will maintain only a single occurrence of the base class, also called sub-object. Let's consider the below example of simpler inheritance where class Point2D inherits Point class.

// example4.cpp
class Point2D:public Point
{
public:
	Point2D( float xval, float yval );
	Point2D();
	float y() const;
protected:
	float _y;
};

The resulting object would look something like the below image 2. Point2D object will contain sub-object Point with its data member and a pointer to a virtual table of Point class.

Image 2: Supporting Inheritance

Based on inheritance hierarchy and complexity, the C++ compiler will add an appropriate offset in the generated virtual table and set vprt accordingly inside the constructor to support the polymorphism. Since run-time binding is a vast topic I will try to address it through another article but referring to the simpler object model of Point2D, now know that C++ supports runtime polymorphism with the clever use of vptr and virtual table.











Reference:
Inside the C++ Object Model (ISBN 0-201-83454-5)











676 views0 comments

Recent Posts

See All

Comments


bottom of page