C++ Class Fundamentals

Classes

Overview: The core concept of classes is data abstraction and encapsulation

class Student {
	public:
		void printId() const {std::cout << "test";};
	private:
};


Defining Member Functions

Member functions are regular functions that belong to a class and are defined within the class body. They can have access specifiers like private, public, or protected.
When calling member functions, consider their accessibility and scope. Private members can only be accessed by other member functions of the same class. Member functions can access all members of the class, including private ones.

class Student {
	public:
		void test() const {std::cout << "test";};
	private:
};


The test function is a member function of the Student class.
The above example defines the function inside the class. It can also be defined out side the class as follows:

class Student {
	public:
		void test() const;
	private:
};

void Student::test() const {
	std::cout << "test";
}


The This Pointer

Every non-static member function receives an implicit pointer parameter pointing to the object instance on which it's called. Inside the function body, all member accesses go through this pointer. Users don't need to pass it explicitly; the compiler handles it automatically.
Within a function, you can use the this pointer to access other members.

Public Access

Public members can be accessed from anywhere in the program.

Private Access

Private members can only be accessed by member functions of the same class.

Protected Access

Protected members have broader access than private but narrower than public. They can be accessed by derived classes and the base class itself.

Constructors


Each class defines how its objects are initialized. These special functions are called constructors. A constructor initializes data members whenever an object is created.

Constructors share the same name as the class and have no return type.

A class can define multiple constructors with different parameters.

Constructor Initializer Lists

A constructor with an initializer list follows this syntax:
ClassName::ConstructorName([parameter list])[:(member initialization list)]

class MyClass{
 public:
 int a,b,c;
 MyClass(): a(1),b(2),c(3)
 {
 }
 MyClass(int x, int y, int z): a(x),b(y),c(z)
 {
 }
};


Note: Constructors initialize constant and reference members.

Function Overloading

Function overloading means functions with the same name but different parameter lists (different function signatures).
Two functions with identical parameter types and counts cannot be overloaded.
Examples: void fun(int a);
void fun(int a,int b);
void fun(double a,int b);
void fun(double a,double b);
void fun(const char* str);
void funn(char* str);

Return types do not affect overloading.
Be cautious with references during overloading. For example: void fun(int x);
void fun(int& x);
Calling fun(a) where a is an int causes ambiguity because both forms match.

Copying Objects

Example: Box box1(box2) or Box box1=box2;

  1. Class-type members use their copy constructors
  2. Built-in types are copied directly
  3. Arrays are copied element by element

A copy constructor is a special constructor taking a reference to the same class type.
If not explicitly defined, a default copy constructor perofrms shallow copying — copying each member directly. Shallow copying works if there are no pointers involved.
However, when pointers exist, shallow copying leads to both objects pointing to the same memory location. This results in double deletion during destruction, causing dangling pointer issues. Therefore, deep copying is required in such cases.

Deep copying allocates new memory for data, preventing dangling pointers.

Copy Constructor

A constructor whose first argument is a reference to the same class and has default values for additional parameters is a copy constructor.
(Using a reference prevents infinite recursion during construction.)

class Foo {
   public:
   	Foo();			// Default constructor
   	Foo(const Foo&);	// Copy constructor
   private:
   	int num;
   
};


Deep copy with pointers

class Foo {
   public:
   	Foo(int n): num(n){
   		str = new char[n];
   	};			// Default constructor
   	Foo(const Foo& c) {
   		num = c.num;
   		str = new char[num];
   		if (str) {
   			strcpy(str, c.str);
   		}
   	};	// Deep copy constructor
   private:
   	int num;
   	char * str;
   
};


Assignment Operators

Copying creates new objects, while assignment modifies existing ones.
Example: Box box1(1, 2, 3), box2(4, 5, 6); box1 = box2;
Assignment operators are invoked on already existing objects.

Destructors


Destructors are automatically called when objects are destroyed, handling cleanup tasks.
The destructor name is the class name prefixed with ~.
Destructors are called by the compiler at the end of an object's lifetime.
Note: Destructors cannot be overloaded.

Use case: Release dynamically allocated resources before an object is destroyed.

class TestClass {
public:
	TestClass(){ str = new char[100];}
	~TestClass(){delete[] str;};
private:
	char * str;
};


Friends

--

Friends provide access to private members of another class.
To make a function a friend, declare it with the friend keyword.

class FriendClass; // Forward declaration

class TargetClass {
public:
   // Public members and functions

private:
   // Private members and functions

   friend class FriendClass; // FriendClass becomes a friend
};


In the above code, FriendClass can access TargetClass's private members.
You can also declare global functions as friends:

class TargetClass {
public:
   // Public members and functions

private:
   // Private members and functions

   friend void friendFunction(TargetClass& obj); // friendFunction becomes a friend
};


Global function friendFunction is a friend of TargetClass, allowing access to its private members.

Friend Functions

Friend functions are global functions declared as friends of a class. They can access private members but aren't class members themselves. They lack an implicit this pointer.

class TargetClass;

class FriendClass {
public:
   void friendFunction(TargetClass& obj); // Declares TargetClass as a friend

   void normalFunction(TargetClass& obj) {
       // Friend function can access TargetClass's private members
       int x = obj.privateVar;
   }
};

class TargetClass {
public:
   TargetClass(int val) : privateVar(val) {}

private:
   int privateVar;

   friend void FriendClass::friendFunction(TargetClass& obj); // Declares friendFunction as friend
};

void FriendClass::friendFunction(TargetClass& obj) {
   // Friend function can access TargetClass's private members
   int x = obj.privateVar;
}


Be cautious with friend functions as they may break encapsulation.

Friend Classes

A friend class can access the private members of another class.

class FriendClass {
public:
   void accessTarget(TargetClass& obj) {
       // Friend class can access TargetClass's private members
       int x = obj.privateVar;
   }
};

class TargetClass {
public:
   TargetClass(int val) : privateVar(val) {}

private:
   int privateVar;

   friend class FriendClass; // FriendClass becomes a friend class
};


Limitations of Friends

  1. Friendship is not transitive.
  2. Friendship is not inherited.
  3. Friendship is unidirectional.
  4. Friends do not alter member visibility.

Static Members

Sometimes a class needs members tied to the class itself rather than individual instances.
For example, a bank account might need a shared interest rate.

Static member functions are not bound to specific objects and do not contain this.

Declaring Static Members

All objects share one storage space for static members, allocated at compile time. Static members don't contribute to object size.
Use static keyword in class definition.

Initializing and Using Static Members

Static member variables must be initialized outside the class:

class Maker
{
public:
	Maker(int d = 0) :data(d)
	{

	}
	void show()
	{
		std::cout << "data:" << data << std::endl;
		std::cout << "count:" << count << std::endl;
	}
	static void print()
	{
		std::cout << "count:" << count << std::endl;
	}
public:
	static int count; // Static data member
private:
	int data;
};
int Maker::count = 0; // Static member initialized outside class

int main()
{
	Maker m;
	m.show(); // Access via member function
	m.print(); // Access via static function
	std::cout << Maker::count << std::endl; // Access via scope resolution
	std::cout << m.count << std::endl;	// Access via object
	
	// sizeof(m) = 4, static member does not take up space
	std::cout << "size:" << sizeof(m) << std::endl; 

	std::cin.get();
	return 0;
}


Static Member Functions

Static functions are associated with the class, not individual objects. They can be called without an object instance using the scope operator.
They cannot access non-static members or functions directly. Only static members and functions are accessible.

class Maker
{
public:
	Maker(int d = 0) :data(d)
	{

	}
	void show()
	{
		std::cout << "data:" << data << std::endl;
		std::cout << "count:" << count << std::endl;
	}
	static void print()
	{
		// Cannot call non-static member function
		// show();  // Error
		
		std::cout << count << std::endl; // OK
		
		// Cannot access non-static member
		// std::cout << data << std::endl; // Error
	}
public:
	static int count;
private:
	int data;
};
int Maker::count = 0;


Base and Derived Classes


Inheritance allows defining new types based on existing ones. Derived classes inherit members from base classes.

Defining Derived Classes

Single inheritance: class Derived : Access Base { };
Multiple inheritance: class Derived : Access1 Base1, Access2 Base2 { };
Access specifiers: public, private, protected

Public Inheritance

Public members of the base class become public in the derived class. Protected members remain protected.

Private Inheritance

Base class public and protected members become private in the derived class.

Multiple Inheritance

A class can inherit from multiple base classes.

Ambiguity and Resolution Rules

When multiple base classes have the same member, ambiguity arises.

#include<iostream>
using namespace std;
class Baseclass1 
{
public:
	void seta(int x) { a = x; }
	void show() { cout << "a=" << a << endl; }
private:
	int a;
};
class Baseclass2
{
public:
	void setb(int x) { b = x; }
	void show() { cout << "b=" << b << endl; }
private:
	int b;
};
class Derivedclass :public Baseclass1, public Baseclass2 
{

};
int main(void)
{
	Derivedclass obj;
	obj.seta(2);
	obj.show(); // Ambiguous
	obj.setb(4);
	obj.show(); // Ambiguous
}


To resolve ambiguity, use scope resolution:

obj.Baseclass1::show();
obj.Baseclass2::show();
}


Virtual Base Classes

When multiple inheritance introduces common base classes, ambiguity occurs. Virtual base classes solve this by ensuring only one subobject exists.

#include<iostream>
using namespace std;
class Base
{
protected:
	int val;
};
class Baseclass1 :public virtual Base
{
public:
	void seta(int x) { val = x; }
};
class Baseclass2 :public virtual Base
{
public:
	void setb(int x) { val = x; }
};
class Deviredclass :public Baseclass1, public Baseclass2
{
public:
	void show();
};
void Deviredclass::show()
{
	cout << "Baseclass val=" << val << endl;
}
int main(void)
{
	Deviredclass obj;
	obj.seta(3);
	obj.show();
	obj.setb(4);
	obj.show();
}


Virtual Functions


Virtual functions enable dynamic binding for polymorphism.

Virtual Tables

Each class with virtual functions contains a virtual table (vtable). Each entry points to a virtual function.
Every object holds a pointer (__vptr) to its class's vtable.
The vtable is constructed at compile time.

class A {

public:

   virtual void vfunc1();

   virtual void vfunc2();

   void func1();

   void func2();

private:

   int m_data1, m_data2;

};



class B : public A {

public:

   virtual void vfunc1();

   void func1();

private:

   int m_data3;


};


class C: public B {

public:

   virtual void vfunc2();

   void func2();

private:

   int m_data1, m_data4;

};


Each clas has its own vtable. Virtual functions are resolved at runtime using these tables. Non-virtual functions bypass the vtable.

Pure Virtual Functions

Pure virtual functions are declared in base classes without implementation. Derived classes must override them.
Syntax: virtual void func() = 0;

class BaseA {
	public:
		virtual void vfunc1() = 0;
	private:
	
}
class DerivedB :public BaseA {
	public:
		void vfunc1() {
			std::cout << "print b" << std::endl;
		};
};


Tags: C++ Classes Constructors destructors Inheritance

Posted on Wed, 20 May 2026 05:00:44 +0000 by chintansshah