You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A class is a user-defined type that serves as a blueprint for creating
objects.
It encapsulates data members (variables) and member functions (methods)
that operate on these data.
A class provides a way to group related behaviors and attributes into a
cohesive unit, promoting data abstraction and encapsulation.
A class helps enforce encapsulation by limiting access to internal details
and exposing only the necessary functionalities, making code more modular,
reusable, and easier to maintain.
An empty object occupy at least one byte memory, since all objects
need to have unique memory addresses. Creating an object on the stack is
faster than on the heap.
Structures( Stored as the class ) + Functions( Stored outside of the class
) = Classes.
Visibility
The visibility makes code more readable, not affect the performance.
private( default visibility ): The owner class, friend class.
protected: The owner class, friend class, sub-class.
public: All.
Declaration Syntax
classClassName;
Definition Syntax
classClassName {
...;
};
Member Variables
Explanation
Member variables, also known as instance variables or fields, are
attributes of a class that hold data specific to the instances (objects)
of that class. They define the state of an object.
Member variables can be assigned default valueswhen they are
defined.
Syntax
classClassName {
private:
Type _mem; // Declaration or definition of a member variable.
};
this
Explanation
this is a pointer available within a class's member function that
points to the object that invoked the member function. It allows access to
the calling object's members.
this is implicitly passed to all non-static member functions.
It can be useful for disambiguation when member variables and parameters
have the same name.
It is not available in static member functions as there is no
associated object.
mutable member variables can be modified even in const member
functions.
This allows certain aspects of an object to change while keeping its overall
state constant.
Generally, they are used for caching or lazy evaluation purposes.
They break the const-correctness for that particular member variable.
In 90% of cases, the mutable keyword will be used in a class with the
const keyword.
Syntax
classClassName {
private:mutable Type _mem; // Declaration or definition of a member variable.
};
Pointer Member Variables
Explanation
Pointer member variables hold memory addresses of instances or other data
types.
They allow dynamic memory management and manipulation of resources.
Syntax
classClassName {
private:
Type* _mem_ptr; // Declaration or definition of a member pointer.
};
Reference Member Variables
Explanation
Reference member variables are alternatives to pointers that refer to
existing objects.
They cannot be reassigned once established.
Theyt must be initialized when defined and cannot be null.
There are only two ways to initialize a reference member:
In-Class Initialization: It can be overridden by the constructor.
Constructor Initialization.
When initializing a reference member in a class, the reference must be
bound to either another member variable of the class or an external
variable passed as a reference during object construction.
override is a keyword used to explicitly indicate that a method
in a derived class is meant to overridea virtual method in the
base class.
It ensures that the method signature in the derived class matches
a virtual method in the base class. If the method signature does not
match, the compiler will generate an error, which helps prevent
subtle bugs caused by accidental method hiding.
Syntax
classBase {
public:virtual RetType funcName( para_list ){
// `virtual` method in base class
};
};
classDerived: publicBase {
public:
RetType funcName( para_list ) override{
// Override the base class method
};
};
Notes
You should always use the override keyword when overriding functions in a
derived class, as it helps prevent errors.
Constructors
Explanation
Constructors are special member functions of a class that are
automatically called when an object of that class is created.
They are used to initialize the object's properties and set up any
necessary resources.
All constructorsexcept for copy and move constructors will cause
default initialization of class members if the constructor lacks an
initializer list.
A parameterized constructor is a type of constructor in a class that
allows you to initialize an object with specific values upon creation.
Unlike a default constructor (which takes no arguments), a parameterized
constructor accepts one or more arguments that enable you to set the
initial state of an object with specific values.
It is called when an object is instantiated with specific arguments.
Constructor initializer lists are used to initialize member variables of
a class in the constructor's initialization phase before the body of the
constructor executes.
Member variables are initialized in the order they are declared in the
class, not the order they appear in the initializer list.
Using initializer lists can be more efficient, as it allows direct
initialization of the member variables, avoiding unnecessary default
construction followed by assignment.
An initializer list is required to initialize const and reference
members, as they cannot be assigned after construction.
If members are initialized in the body of the constructor, they are
first default-initialized before being assigned.
Initialization in the constructor body can result in additional
overhead compared to direct initialization in the initializer list.
// Modern initializer lists.// Direct-list-initialization (initial value in braces).// C++11 uniform initialization.// Type safe, prevents narrowing conversions.// Recommended.classClassName {
public:ClassName( Type1 para1, Type2 para2, ... ):
_mem1{ para1 }, _mem2{ para2 }, ...{};
private:
Type1 _mem1;
Type2 _mem2;
...;
};
Differences between Traditional Initializer Lists and Modern Initializer Lists
Traditional initializer lists:
They were used before C++11 and primarily involved constructor
initialization lists to initialize member variables, with less
emphasis on consistency across all types.
Initialization order:
Matched constructor:
The constructor that best matches the initializer list is chosen first.
Implicit conversion:
If no exact match is found for the constructor, implicit conversions
can be applied, and the constructor that matches the converted type is
chosen.
Modern initializer lists (Introduced in C++11):
Thye bring a more consistent, type-safe, and uniform syntax for
initialization.
They prevent narrowing conversions and provide a cleaner syntax
that can be used across all types (primitives, containers, user-defined
types).
They can work with std::initializer_list< Type > to initialize
containers like std::vector, but std::initializer_list< Type >introduces a slight overhead. This overhead arises because
std::initializer_list< Type >stores a pointer to the underlying data
and the size of the list. However, this overhead is generally minimal and
is unlikely to significantly affect performance in most use cases.
This is only used to initialize the values of elements in an object and
cannot specify the size of the object, except when the size itself is a
member of the object.
std::vector< int > vec{ val }; // Create a vector with one element.
std::vector< int > vec( size ); // Create a vector with `size` elements.
An example:int int_var{ val };.
Initialization order:
Constructor with an initializer list:
If the class has a constructor that takes a std::initializer_list, it
will be prioritized when initializing with curly braces and matching
arguments.
Other constructors: contain the implicition conversion.
If the initializer list constructor is not available or does not match,
the compiler looks for a regular constructor that matches the
arguments.
Aggregate initialization:
If no constructors are defined, aggregate initialization applies to
structs or classes.
Limitations of Aggregate Initialization
No user-defined constructors:
Aggregate initialization is only applicable to aggregate types that lack
user-defined constructors.
If a class has any constructor defined, aggregate initialization cannot be
used.
Public members only:
Only public members can be initialized via aggregate initialization.
Private or protected members cannot be accessed.
No default member initializers:
Default member initializers in aggregates are ignored during aggregate
initialization, meaning you must explicitly specify values for all members.
Order of initialization:
Members are initialized in the order they are declared.
This can lead to issues if one member relies on another being initialized
first.
No designated initializers:
C++ does not support designated initializers (as in C99), preventing the
ability to initialize specific members without initializing all preceding
members.
Type matching:
The types in the initializer list must match the types of the aggregate's
members exactly.
A mismatch results in a compilation error.
No implicit conversions:
Aggregate initialization does not allow implicit type conversions, which
can limit flexibility.
No inheritance:
Aggregate initialization is not applicable for derived classes, limiting
its use in inheritance scenarios.
Notes
In the constructor initializer list, you'd better initialize the members in
the order in which they are defined in the class.
If a class has a class or struct member, we should use the constructor
initializer to initialize the class or struct member; otherwise, the
initialization will call the object's constructors twice: first the
default constructor, and then the other constructor.
Copy Constructors
Explanation
A copy constructor is a special constructor that initializes a new
object as a copy of an existing object.
This is essential for managing resource ownership and ensuring that objects
behave properly when passed, returned, or assigned.
The parameter of a copy constructor and copy assignment operator should
not be passed by value, as this can lead to issues.
When passing the argument by value, the copy constructor will be called to
create a temporary object for the parameter.
This temporary object creation requires another call to the copy
constructor, which leads to a recursive cycle.
The cycle continues until the call stack overflows, causing a stack
overflow error.
In other words, passing by value in the copy constructor or copy assignment
operator triggers an infinite loop, as each invocation requires another
copy of the argument, which again invokes the copy constructor.
A default copy constructor is automatically generated by the compiler if
no user-defined copy constructor is present.
It performs a shallow copy of the object's members.
For primitive types, this is a straightforward copy, but for pointer
members, both the original and copied objects will reference the same
memory.
This can lead to issues such as:
Double destructions: When both objects are destroyed, they
attempt to free the same memory location, resulting in undefined
behavior.
Dangling pointers: When one object is deleted, the other
retains a pointer to the deallocated memory, leading to program
crashes if accessed.
The copy is performed quickly since it merely copies the pointer (for
pointer types) rather than the pointed-to values.
The default copy constructor is typically marked as noexcept, improving
performance by ensuring compatibility with standard containers, such as
std::vector.
The = default syntax explicitly requests the compiler to generate the
default copy constructor.
A deep copy ensures that the copy constructor creates a new instance
of any dynamically allocated memory, so that two objects do not reference
the same location.
This preventsdouble deletion and dangling pointer issues, as
each object manages its own memory independently.
Although slower than shallow copying due to memory allocation, deep
copying is generally much safer, particularly for complex data
structures.
When implementing a copy constructor, it is also recommended to
implement a corresponding copy assignment operator to maintain consistent
behavior and proper resource management.
A move constructor is a constructor that transfers resources from one
object to another, rather than performing a deep copy.
This enables efficient handling of temporary objects and avoids
unnecessary data copying.
It is ideal for rvalue references, which represent temporary objects that
do not need to retain their original state.
Move constructors should be marked noexcept to ensure compatibility
with standard containers and to optimize performance.
Move constructors behave similarly to shallow copy constructors but use
move semantics and rvalue references.
A shallow move resembles a shallow copy but without assigning all
pointers in the source object to nullptr, whereas a deep move
resembles a shallow copywith all pointers in the source object being
assigned to nullptr.
Default Move Constructors
Explanation
The compiler generates a default move constructor only if:
The class does not explicitly define a copy constructor, copy assignment
operator, move constructor, or move assignment operator.
All data members and base classes are movable (i.e., they also
support move semantics).
The default move constructor performs a shallow move, transferring (not
copying) each member from the source object to the destination.
However, it treats all members as if they were primitive types, performing
a shallow transfer without resetting any pointer members of the original
object to nullptr.
This can cause issues such as double deletions and dangling pointers.
While terms like "shallow move" and "deep move" are not part of standard
C++ terminology, "unsafe move" and "safe move" better convey the risks and
benefits of different copying strategies.
The default move constructor is typically marked as noexcept, improving
performance by ensuring compatibility with standard containers, such as
std::vector.
The = default syntax explicitly requests the compiler to generate the
default move constructor.
// Not recommend, although the compiler may optimize it so that only one constructor is called.// First, parameterized constructor.// Second, copy constructor.
ClassName obj_name = ClassName( para_list );
// Without related parameterized constructors and all members are public, aggregate initialization.// With related parameterized constructors, parameterized constructor or uniform initialization or brace initialization.
ClassName obj_name{ para_list };
// With default construcotr, default constructor.// Otherwise, aggregate initialization.// All members are initialized to 0.
ClassName obj_name{ };
// Without related parameterized constructors and all members are public, aggregate initialization.// With related parameterized constructors, parameterized constructor or uniform initialization or brace initialization.
ClassName obj_name = { para_list };
// When defining a class, assigning default values to its members.classClassName {
private:
Type _mem1 = initializer1;
...;
};
classClassName {
public:// Move constructor definition.
...;
// Factory methods for initialization.static ClassName createClassName() {
returnstd::move( ClassName( para_list ) ); // Parameterized constructor.
};
// Only used to return an existing object.// static ClassName&& createClassName() {// return std::move( existing_obj ); // Parameterized constructor.// };
...;
};
// First, create a temporary object with the parameterized constructor in the createClassName.// Second, create `obj_name` with the move constructor.
ClassName obj_name = ClassName::createClassName();
Class Pointers
Declaration Syntax
ClassName* obj_ptr;
Definition or Initialization Syntax
// Default constructor.
ClassName* obj_ptr = new ClassName;
// Parameterized constructor.
ClassName* obj_ptr = new ClassName( para_list );
How to Determine Which Constructor or Assignment Operator Is Invoked
If an object is being defined, the appropriate constructor will be
called:
No argument: default constructor.
Single argument of a different type: conversion constructor.
Single argument of the same type (lvalue reference): copy
constructor.
Single argument of the same type (rvalue reference): move
constructor.
Multiple arguments: parameterized constructor.
If there is no explicit keyword before the called constructor, implicit
conversion occurs, and the five rules mentioned above are followed to
determine which constructor will be called.
If an object already exists and is assigned a new object, the
assignment operator will be called:
A different-type object: conversion operator.
Same type (lvalue reference): copy-assignment operator.
Same type (rvalue reference): move-assignment operator.
If there is no explicit keyword before the called assignment operator,
implicit conversion occurs, and the four rules mentioned above are followed
to determine which assignment operator will be called.
When a temporary object is passed to a function by value, its copy
constructor is not called due to move semantics or copy elision(especially
since C++17). Instead, the move constructor (if available) or a
parameterized constructor (if the move constructor is not available) is
called.
Inheritance
Explanation
Inheritance allows a class to acquire the properties and
behaviors of another class.
It promotes code reuse and establishes a hierarchical relationship
between a base class and one or more derived classes.
C++ supports multiple inheritance and virtual inheritance to
address complexities arising from overlapping base classes.
Inheritance also facilitates polymorphism, enabling derived classes to
override virtual functions.
Definition Syntax
Code
classBase {
// Members of base class.
};
classDerived: publicBase {
// Members of derived class.
};
template< classT, ... >
classBase {
// Members of base class.
};
template< classT, ... >
classDerived: publicBase< classT, ... > {
// Members of derived class.
};
Categories
public inheritance: The visibility of base class members remains
unchanged in the derived class.
protected inheritance: Public members of the base class become
protected in the derived class.
private inheritance: All base class members (public and protected)
become private in the derived class.
A virtual method is a member function declared with the virtual
keyword in a base class, allowing derived classes to provide their
own implementation.
When invoked through a base class pointer or reference, the function from
the most derived class is called, enabling runtime polymorphism.
virtual methods ensure dynamic dispatch by resolving function calls
at runtime based on the object's actual type.
They can be overridden in derived classes using the same function
signature.
The override keyword is optional but recommended, as it ensures
that the derived class correctly overrides a virtual function.
If a derived class does not override the virtual function, the base
class's implementation will be invoked.
Once a function is declared as virtual in a base class, it remains
virtual in all derived classes until the final keyword is encountered.
The virtual keyword propagates down the class hierarchy.
Syntax
classBase {
public:virtual RetType funcName() const {
// Base class implementation.
};
};
classDerived: publicBase {
public:// Override keyword for clarity.
RetType funcName() constoverride{
// Derived class implementation.
};
};
Static Binding and Dynamic Binding
Polymorphism and Problems
Polymorphism:
virtual functions enable polymorphic behavior, allowing a program to
treat objects of different derived classes through a base class pointer or
reference.
This allows different objects to be operated on using the same
interface, meaning the same operation can be applied to different
objects, exhibiting different behaviors.
This makes it easier to write flexible and reusable code.
Performance overhead:
virtual functions introduce a performance penalty due to the dynamic
dispatch mechanism.
The program must go through the V table to look up the correct function
to call at runtime, which can be slower than static binding.
Memory overhead:
Each class that uses virtual functions typically includes a virtual
table (vtable), which adds some memory overhead.
Each object of such classes contains a pointer to the vtable, increasing
the size of the object.
Debugging Challenges:
Debugging issues related to virtual functions can be more challenging,
especially when it comes to understanding the flow of execution and object
lifetimes in the presence of polymorphism.
Potential for Resource Leaks:
If a base class has virtual functions, its destructor should
also be virtual to ensure proper cleanup of derived class resources.
Failing to declare a virtual destructor can lead to resource leaks.
A virtual destructor ensures that when an object is deleted via
a base class pointer, the destructor of the derived class is
invoked, followed by the base class’s destructor.
This guarantees that all resources allocated by the derived class are
correctly released, thereby preventing memory leaks and ensuring
complete destruction of the object.
Destructors should be declared virtual in any base class intended for
inheritance to avoid undefined behavior during object destruction.
Without a virtual destructor, only the base class
destructor would execute, leaving resources specific to the
derived class unreleased.
Although virtual destructors add a slight performance overhead, they
are critical for safe and correct resource management.
Syntax
classBase {
public:virtual~Base() {
// Cleanup logic for the base class.
};
};
classDerived: publicBase {
public:~Derived() {
// Cleanup logic for the derived class.
};
};
Notes
It's used to delete a subclass object pointed to by a parent class
pointer in a polymorphic context. A virtual destructor differs from
other virtual functions.
The subclass's virtual destructor does not override its parent class's
virtual destructor; both will be called.
If we allow our class to be derived into a subclass, we should
declare its destructor as virtual to avoid issues like memory
leaks.
virtual Inheritance
Explanation
virtual inheritance prevents ambiguity when a class indirectly
inherits from the same base classmultiple times.
It ensures that only one instance of the base class exists in the
final derived class.
It resolves the diamond inheritance problem, where two base classes share
a common ancestor.
The base class constructor must be invoked from the most-derived
class.
Virtual inheritance occurs only when a class is inherited using the
virtual keyword, and it propagates down the class hierarchy.
For example, Base is virtually inherited by B using the virtual
keyword. Although Derived inherits from B without using the virtual
keyword, Base is still virtually inherited by Derived through B, as B
has virtually inherited Base.
The virtual keyword propagates down the class hierarchy.
Definition Syntax
classBase {
...;
};
classA: virtual public Base {};
classB: virtual public Base {};
classDerived: publicA,
public B {};
Initialization Syntax
classDerived: publicA,
public B {
public:Derived( para_list1 ):
Base( para_list2 ), A( para_list3 ), B( para_list4 ){};
};
Construction Order
virtual base class constructor: Called first, even if it's inherited
indirectly.
Other base class constructors: Called next.
Derived class constructor: Called last.
Destruction Order
Derived class destructor: Invoked first.
Non-virtual base class destructors: Called next.
virtual base class destructor: Called last.
friend
Explanation
The friend keyword grants non-member functions or other classesaccess to the private and protected members of a class.
The friend keyword is commonly used when specific functions or classes
require access to a class's internal details but are not part of its public
interface
Friendship in C++ is not inherited—a derived class does not inherit
the friendship relationships of its base class.
Additionally, friendship is unidirectional: the class or function granted
access does not automatically reciprocate that privilege.
friend Functions
Explanation
A friend function is a non-member function that is allowed access to
the private and protectedmembers of a class.
If the friend function needs access to non-static non-public members, the
class instance should be passed to the friend function.
If the friend function only accesses static members or performs
operations unrelated to a specific object, the class instance does
not need to be passed.
classClassName {
public:// Friend function declaration.friend RetType funcName();
};
friend Classes
Explanation
A friend class is a non-member class that is granted access to the
private and protected members of another class.
If the member functions of the friend class need to access non-static
non-static members, the class instance should be passed to those
functions.
If the member functions of the friend class access static members or
perform operations unrelated to a specific object, the class instance
does not need to be passed.
classClassA {
public:friendclassClassB; // Declaring ClassB as a friend
};
classClassB {
public:
RetType funcName() { ... };
};
final
Explanation
The final keyword in C++ is used to prevent further inheritance or
overriding of classes and virtual functions.
It plays a crucial role in controlling the behavior of classes in the
inheritance hierarchy.
final Functions
Explanation
A final function is a virtual function that cannot be overridden
by any derived class.
The final specifier ensures that the function's implementation remains
fixed in the class that declares it as final.
This feature can improve performance by devirtualizing calls to the
final function, as the compiler can safely inline such calls, knowing
that no further overrides exist and not add this function to vtable.
Syntax
classBase {
public:virtual RetType funcName() final; // Declaration of a final function.
};
final Classes
Explanation
A final class is a class that cannot be inherited from.
Declaring a class as final ensures that no further subclassing is allowed,
which can prevent misuse of inheritance and unintended extensions of the
class’s functionality.
This can improve runtime performance, as the compiler can optimize
calls and remove virtual dispatch overhead.
final classes are often used for singleton patterns, utility
classes, or performance-critical components to prevent inheritance
overhead.
Syntax
classFinalClassfinal {};
// class Derived: public FinalClass {}; // Error: Cannot inherit from a final class
Notes
final functions should not be combined with the override keyword, but
virtual methods in final classes should still be declared with the
override keyword.
final classes do not need to contain functions with the final keyword.
Functions marked with override must be virtual functions.
A bitfield in C++ is a data structure that allows you to allocate a
specific number of bits for individual memberswithin a struct or
class.
This is particularly useful for saving memory when you need to store
multiple small values that can fit into fewer bits than the size of the
default data types (e.g., int, char).
Bitfields are commonly used for:
Flags.
Control registers.
Packed data structures.
Type is an integral type (e.g., int, unsigned int, short).
mem_name is the name of the bitfield member.
num_of_bits is the number of bits allocated to this member.
The size of ClassName is equal to (memory alignment)
ceil( (double)( num_of_bits1 + num_of_bits2 + ... ) / sizeof( maximum-size type ) ) * sizeof( maximum-size type );.