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

Basic CPP

cpp related all notes for interveiw purpose

Uploaded by

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

Basic CPP

cpp related all notes for interveiw purpose

Uploaded by

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

1.

Difference Between C and C++

• Paradigm: C is a procedural programming language, whereas C++ is a multi-


paradigm language that supports both procedural and object-oriented programming.
• OOP Support: C does not support Object-Oriented Programming (OOP) concepts
like classes, objects, inheritance, and polymorphism. C++ was designed with OOP
support, enabling more modular and reusable code.
• Standard Libraries: C has a limited standard library mainly focused on basic
functions. C++ offers the Standard Template Library (STL), which provides a rich set
of data structures and algorithms.
• Memory Management: In C, memory management is manual (using malloc and
free), while C++ supports both manual and automatic memory management (using
new/delete and smart pointers).
• Namespaces: C does not have namespaces, while C++ uses them to avoid name
conflicts.
• Function Overloading and Operator Overloading: These features are not present in
C but are supported in C++.

2. Concept of OOP (Object-Oriented Programming) in C++

Object-Oriented Programming (OOP) is a programming paradigm based on the concept of


objects, which contain both data (attributes) and functions (methods) that operate on the data.
The main principles of OOP in C++ are:

• Encapsulation: Bundling data and methods that operate on the data within a single
unit (class) and restricting access to some of the object's components. It helps to hide
the internal state of the object.
• Abstraction: Simplifying complex systems by modeling classes appropriate to the
problem, focusing on essential features while hiding unnecessary details.
• Inheritance: Creating new classes from existing classes, enabling code reusability
and the creation of a class hierarchy.
• Polymorphism: Allowing objects to be treated as instances of their parent class. It
supports dynamic method binding, where a function call can invoke different methods
depending on the object type.

3. Constructors and Destructors

• Constructors: Special member functions of a class that are automatically called when
an object is created. They initialize the object's data members. Constructors can be
overloaded to allow different ways of initializing an object.

cpp
Copy code
class Example {
public:
int x;
// Constructor
Example(int val) : x(val) {}
};
• Destructors: Special member functions that are automatically called when an object
is destroyed. They are used for cleanup, such as freeing resources or memory.

cpp
Copy code
class Example {
public:
int* ptr;
// Constructor
Example(int val) {
ptr = new int(val);
}
// Destructor
~Example() {
delete ptr;
}
};

4. Difference Between a Class and a Struct in C++

• Default Access Modifier: In C++, the members of a class are private by default,
while in a struct, the members are public by default.
• Usage: Classes are used for complex data structures that encapsulate both data and
methods, while structs are generally used for simple data structures that primarily
hold data.
• Inheritance: Both classes and structs support inheritance in C++, but the default
visibility for inheritance is private for classes and public for structs.

5. Access Specifiers: Public, Private, and Protected

• Public: Members declared as public are accessible from anywhere outside the class.
• Private: Members declared as private are accessible only within the class itself.
• Protected: Members declared as protected are accessible within the class and by
derived classes.

Example:

cpp
Copy code
class Example {
public:
int publicVar; // Accessible anywhere
private:
int privateVar; // Accessible only within the class
protected:
int protectedVar; // Accessible within the class and derived classes
};

6. The "this" Pointer

• The this pointer is an implicit parameter to all non-static member functions in C++.
It points to the object for which the member function is called.
• It is used to differentiate between member variables and parameters with the same
name, and for chaining member functions.
• Example:

cpp
Copy code
class Example {
public:
int value;
Example(int value) {
this->value = value; // Using 'this' to differentiate between
parameter and member variable
}
Example& setValue(int value) {
this->value = value;
return *this; // Returning the current object
}
};

These concepts are fundamental to understanding C++ and writing efficient, object-oriented
code.

7. Difference Between Stack and Heap Memory

• Stack Memory:
o Structure: Follows a Last-In-First-Out (LIFO) structure for managing
memory.
o Automatic Allocation: Memory allocation and deallocation are done
automatically when a function is called or exits. Local variables are stored on
the stack.
o Size Limitations: Limited in size, and exceeding the stack's capacity can
cause a stack overflow.
o Access Speed: Faster access compared to heap memory due to its simple
memory management.
o Lifetime: Memory is automatically freed when the function exits, and
variables go out of scope.
• Heap Memory:
o Structure: Memory is dynamically allocated and managed by the
programmer.
o Manual Allocation: Requires explicit allocation (new or malloc) and
deallocation (delete or free).
o Size Flexibility: Generally larger and more flexible than stack memory.
o Access Speed: Slower access compared to stack memory due to the
complexity of dynamic memory management.
o Lifetime: Memory must be explicitly freed by the programmer; otherwise, it
will remain allocated until the program terminates.

8. How to Allocate and Deallocate Memory in C++

• Stack Memory Allocation:


o Variables defined in a function are automatically allocated on the stack.
o Example:

cpp
Copy code
int a = 10; // Allocated on the stack

• Heap Memory Allocation:


o Use new to allocate memory and delete to free memory.
o Example:

cpp
Copy code
int* ptr = new int; // Allocating an integer on the heap
*ptr = 20;
delete ptr; // Deallocating the memory

• Array Allocation on the Heap:

cpp
Copy code
int* arr = new int[10]; // Allocating an array on the heap
delete[] arr; // Deallocating the array memory

9. Smart Pointers in C++

Smart pointers are classes that manage dynamically allocated memory, automatically
handling memory deallocation. They help prevent memory leaks by ensuring proper cleanup
of resources.

• std::unique_ptr:
o Represents exclusive ownership of a dynamically allocated object.
o Only one std::unique_ptr can manage a specific resource, and it cannot be
copied but can be moved.
o Example:

cpp
Copy code
std::unique_ptr<int> ptr = std::make_unique<int>(10);

• std::shared_ptr:
o Represents shared ownership of a dynamically allocated object.
o Multiple std::shared_ptr instances can manage the same resource. The
resource is deallocated when the last std::shared_ptr goes out of scope.
o Example:

cpp
Copy code
std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
std::shared_ptr<int> ptr2 = ptr1; // Both ptr1 and ptr2 share
ownership

• std::weak_ptr:
o A non-owning reference to a resource managed by a std::shared_ptr.
o Prevents circular references by allowing access to a resource without
increasing its reference count.
o Use lock() to convert a std::weak_ptr to a std::shared_ptr if the
resource is still available.
o Example:

cpp
Copy code
std::shared_ptr<int> sharedPtr = std::make_shared<int>(30);
std::weak_ptr<int> weakPtr = sharedPtr; // Does not increase
reference count

10. Memory Leak and Its Prevention

• Memory Leak:
o Occurs when dynamically allocated memory is not properly deallocated,
causing the memory to remain inaccessible even though it's no longer needed.
o Over time, memory leaks can exhaust available memory, leading to poor
performance or application crashes.
• Prevention Techniques:
o Manual Deallocation: Ensure every new or malloc has a corresponding
delete or free.
o Use of Smart Pointers: Utilizing smart pointers like std::unique_ptr and
std::shared_ptr automates memory management, significantly reducing the
risk of memory leaks.
o Avoid Raw Pointers: Prefer smart pointers over raw pointers for managing
dynamic memory.
o Static Analysis Tools: Use tools like Valgrind or AddressSanitizer to detect
memory leaks during development.

11. Difference Between Pointers and References in C++

• Pointers:
o A pointer is a variable that stores the memory address of another variable.
o It can be reassigned to point to different memory addresses.
o Supports pointer arithmetic (e.g., incrementing to point to the next memory
location).
o Can be nullptr (i.e., it does not point to any memory location).
o Needs to be dereferenced using the * operator to access the value stored at the
memory address.
o Example:

cpp
Copy code
int a = 5;
int* ptr = &a; // ptr points to the memory address of a

• References:
o A reference is an alias for an existing variable and cannot be reassigned to
refer to a different variable once initialized.
o Must be initialized when declared.
o Does not support pointer arithmetic.
o Cannot be nullptr; it must always refer to a valid object.
o Automatically dereferenced when used, making syntax simpler.
o Example:
cpp
Copy code
int a = 5;
int& ref = a; // ref is an alias for a

12. Using the const Keyword with Pointers and References

The const keyword can be used with pointers and references to indicate that the value being
pointed to or referred to should not be modified.

• Pointers:
o Pointer to a const value: The value pointed to by the pointer cannot be
modified, but the pointer itself can be changed to point to another variable.

cpp
Copy code
const int* ptr = &a; // Pointer to a const int
*ptr = 10; // Error: cannot modify the value
ptr = &b; // Allowed: can change the address stored in ptr

o const pointer to a value: The pointer itself cannot be changed to point to


another variable, but the value it points to can be modified.

cpp
Copy code
int* const ptr = &a; // Const pointer to an int
*ptr = 10; // Allowed: can modify the value
ptr = &b; // Error: cannot change the address stored in ptr

o const pointer to a const value: Neither the pointer nor the value it points to
can be modified.

cpp
Copy code
const int* const ptr = &a; // Const pointer to a const int
*ptr = 10; // Error: cannot modify the value
ptr = &b; // Error: cannot change the address stored in ptr

• References:
o const reference: A reference to a constant value that cannot be modified.

cpp
Copy code
const int& ref = a; // Const reference to an int
ref = 10; // Error: cannot modify the value

13. Dangling Pointers and How to Avoid Them

• Dangling Pointer:
o A dangling pointer is a pointer that refers to a memory location that has been
deallocated or released. Accessing or modifying such a memory location leads
to undefined behavior.
o Dangling pointers typically occur in scenarios such as:
▪ Deleting a dynamically allocated object and not setting the pointer to
nullptr.
▪ Returning a pointer to a local variable from a function.
▪ Using a pointer after the object it points to has gone out of scope.
• Avoiding Dangling Pointers:
o Set Pointers to nullptr After Deleting: After freeing memory with delete,
set the pointer to nullptr to avoid accidentally accessing invalid memory.

cpp
Copy code
int* ptr = new int(10);
delete ptr;
ptr = nullptr; // Prevents dangling

o Avoid Returning Pointers to Local Variables: Returning a pointer to a local


variable can result in a dangling pointer because the local variable's memory is
deallocated when the function exits.

cpp
Copy code
int* getPointer() {
int a = 10;
return &a; // Dangerous, returns address of a local
variable
}

o Use Smart Pointers: Smart pointers (std::unique_ptr, std::shared_ptr)


automatically manage memory and help avoid dangling pointers by ensuring
proper deallocation.
o Careful Use of References: When using references, ensure that the referred-
to object remains in scope for as long as the reference is needed.

14. Common Containers in the C++ Standard Library (STL)

The STL provides several containers for storing and managing collections of data. Some
common containers include:

• vector:
o A dynamic array that can resize itself automatically when elements are added
or removed.
o Supports random access to elements in constant time (O(1)).
o Efficient for insertion and deletion at the end but slower for inserting or
deleting elements in the middle (O(n)).
• list:
o A doubly linked list that allows for efficient insertion and deletion of elements
from any position (O(1) for insertions/deletions).
o Does not support random access (O(n) time to access an element).
• deque (double-ended queue):
o Supports fast insertions and deletions at both ends (O(1)).
o Provides random access similar to a vector.
• map:
o An associative container that stores key-value pairs, with unique keys in sorted
order.
o Uses a self-balancing binary search tree internally (usually a Red-Black Tree).
• unordered_map:
o Similar to map but does not maintain any specific order for the elements.
o Uses a hash table internally for fast insertion and lookup.
• set:
o Stores unique elements in a sorted order.
o Allows fast lookup, insertion, and deletion (O(log n)).
• unordered_set:
o Stores unique elements without maintaining any particular order.
o Uses a hash table for fast insertion and lookup (O(1) average time
complexity).

15. Difference Between vector and list

Aspect vector list


Underlying Data
Dynamic array Doubly linked list
Structure
Memory Allocation Contiguous memory Non-contiguous memory
Supported (O(1) access
Random Access Not supported (O(n) access time)
time)
Fast at the end (O(1)), slow Fast at any position (O(1)
Insertion/Deletion
in the middle (O(n)) insertion/deletion)
Lower overhead, no extra
Memory Overhead Higher overhead due to extra pointers
pointers
When frequent access is When frequent insertions or deletions
When to Use
needed or resizing is limited are needed at arbitrary positions

16. How a map Works and Its Time Complexity for Insertion and Lookup

• Structure:
o A map is an associative container that stores key-value pairs with unique keys,
where each key is associated with a specific value.
o Internally, a map uses a self-balancing binary search tree (usually a Red-Black
Tree), which keeps the elements sorted based on the key.
• Time Complexity:
o Insertion: Insertion of an element takes O(log n) time because the tree needs
to be adjusted to maintain balance.
o Lookup: Lookup for a key also takes O(log n) time, as it requires traversing
the tree.
o Deletion: Deleting an element has a time complexity of O(log n).
• Usage Example:

cpp
Copy code
std::map<int, std::string> myMap;
myMap[1] = "One";
myMap[2] = "Two";
// Lookup
std::string value = myMap[1]; // O(log n)

17. Difference Between unordered_map and map

Aspect map unordered_map


Underlying Data Self-balancing binary search
Hash table
Structure tree (Red-Black Tree)
Order of Elements Sorted order based on keys No specific order
Time Complexity O(log n) O(1) on average, O(n) in worst case
(Insertion)
Time Complexity O(log n) O(1) on average, O(n) in worst case
(Lookup)
Memory Overhead Higher due to tree structure Lower overhead due to hash table
When elements need to be in When fast access is needed and
Use Case
sorted order order is not important

18. Polymorphism in C++

Polymorphism is the ability of a function, object, or variable to take multiple forms. In C++,
it allows a base class reference or pointer to refer to objects of derived classes, enabling the
calling of derived class functions through base class references. Polymorphism is a key
concept in Object-Oriented Programming (OOP) and is mainly achieved through function
overloading, operator overloading, and inheritance.

Example of Polymorphism Using Inheritance:

cpp
Copy code
#include <iostream>
using namespace std;

// Base class
class Shape {
public:
virtual void draw() {
cout << "Drawing a shape" << endl;
}
};

// Derived class
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing a circle" << endl;
}
};

// Derived class
class Rectangle : public Shape {
public:
void draw() override {
cout << "Drawing a rectangle" << endl;
}
};

int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Rectangle();

shape1->draw(); // Calls Circle's draw method


shape2->draw(); // Calls Rectangle's draw method

delete shape1;
delete shape2;

return 0;
}

In the example above, polymorphism allows different derived class methods (Circle and
Rectangle) to be called through a base class (Shape) pointer.

19. Difference Between Compile-Time and Run-Time Polymorphism

• Compile-Time Polymorphism:
o Achieved using function overloading and operator overloading.
o The decision about which function or operator to use is made during compile
time.
o Function Overloading: Multiple functions with the same name but different
parameters.

cpp
Copy code
void print(int i) {
cout << "Integer: " << i << endl;
}

void print(double d) {
cout << "Double: " << d << endl;
}

o Operator Overloading: Overloading operators for user-defined data types.


• Run-Time Polymorphism:
o Achieved using virtual functions and inheritance.
o The decision about which function to invoke is made during run time.
o It is enabled by having a base class pointer or reference point to a derived
class object.
o Virtual functions are used in the base class and are overridden in the derived
class.

20. Virtual Inheritance

• Virtual Inheritance is a mechanism to solve the problem of multiple inheritance,


where a derived class indirectly inherits the same base class more than once.
• It prevents multiple "instances" of a base class from being inherited when using
multiple inheritance.
Example Without Virtual Inheritance:

cpp
Copy code
class Base {
public:
int data;
};

class Derived1 : public Base { };

class Derived2 : public Base { };

class Final : public Derived1, public Derived2 { // Two copies of Base


exist in Final
// Base's data is present in both Derived1 and Derived2
};

In the above scenario, Final contains two separate copies of Base. This can lead to
ambiguity when accessing members of the Base class.

Using Virtual Inheritance to Solve the Issue:

cpp
Copy code
class Base {
public:
int data;
};

class Derived1 : virtual public Base { };

class Derived2 : virtual public Base { };

class Final : public Derived1, public Derived2 { // Only one copy of Base
is inherited
// No ambiguity when accessing Base's members
};

21. The Diamond Problem in C++

• The diamond problem arises when a class is derived from two classes that share a
common base class.
• This leads to ambiguity because the derived class has two copies of the base class.

Example of the Diamond Problem:

cpp
Copy code
class A {
public:
void display() {
cout << "Class A" << endl;
}
};

class B : public A { };
class C : public A { };

class D : public B, public C { // Inherits A through both B and C


// Ambiguity: Which version of A's display() should be called?
};

In the above example, class D inherits from both B and C, which in turn inherit from A. This
results in two copies of A in D, leading to ambiguity when calling A's methods.

Solving the Diamond Problem Using Virtual Inheritance:

cpp
Copy code
class A {
public:
void display() {
cout << "Class A" << endl;
}
};

class B : virtual public A { };

class C : virtual public A { };

class D : public B, public C { // Only one copy of A is inherited


// No ambiguity when calling A's display()
};

22. How to Overload Operators in C++

Operator overloading in C++ allows developers to redefine the behavior of operators for user-
defined types (e.g., classes). It enables the use of standard operators like +, -, ==, etc., with
custom classes, making the code more intuitive and readable. The overloaded operator is
implemented as a function, either as a member function or a non-member (friend) function.

Syntax for Overloading Operators:

• Member Function: If the left operand is an object of the class, the operator can be
overloaded as a member function.
• Non-Member Function: If the left operand is not an object of the class (e.g., primitive type),
a non-member function is used.

23.Example of Overloading the + Operator for a Complex Class:

cpp
Copy code
#include <iostream>
using namespace std;

class Complex {
public:
double real, imag;

Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}


// Overload + operator to add two Complex objects
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}

void display() const {


cout << real << " + " << imag << "i" << endl;
}
};

int main() {
Complex c1(3.0, 4.0);
Complex c2(1.0, 2.0);
Complex c3 = c1 + c2; // Uses the overloaded + operator

c3.display(); // Output: 4.0 + 6.0i

return 0;
}

In this example, the + operator is overloaded for the Complex class to add the real and
imaginary parts of two Complex objects.

24. The Rule of Three (or Five) in C++

The Rule of Three (or Rule of Five) is a guideline for managing resources in C++ classes
that handle dynamic memory or other resources. It states that if a class requires a user-defined
destructor, copy constructor, or copy assignment operator, then it likely requires all three.
In C++11 and later, the Rule of Five extends this to include the move constructor and move
assignment operator.

Rule of Three Components:

1. Destructor:
o Cleans up resources when an object goes out of scope.
o If a class allocates memory dynamically, it needs a destructor to free that memory.
2. Copy Constructor:
o Creates a new object as a copy of an existing object.
o Needed to handle deep copying when objects are copied.
3. Copy Assignment Operator (operator=):
o Assigns values from one object to another already existing object.
o Should handle self-assignment and manage any previously allocated resources.

Rule of Five Extensions:

4. Move Constructor:
o Transfers resources from a temporary object (rvalue) to a new object.
o Leaves the source object in a valid but unspecified state.
5. Move Assignment Operator:
o Transfers resources from a temporary object to an existing object.
o Handles resource cleanup for the existing object.
Why Is It Important? The Rule of Three/Five ensures that classes manage resources
correctly, avoiding issues like memory leaks, double deletion, or undefined behavior. It is
especially important for classes that manage dynamic memory, file handles, sockets, or any
other resources that need explicit management.

Example Implementing the Rule of Five:

cpp
Copy code
#include <iostream>
#include <cstring>
using namespace std;

class String {
private:
char* data;
size_t length;

public:
// Constructor
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}

// Destructor
~String() {
delete[] data;
}

// Copy Constructor
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}

// Copy Assignment Operator


String& operator=(const String& other) {
if (this == &other) {
return *this; // Self-assignment check
}

delete[] data; // Free existing memory

length = other.length;
data = new char[length + 1];
strcpy(data, other.data);

return *this;
}

// Move Constructor
String(String&& other) noexcept : data(other.data),
length(other.length) {
other.data = nullptr; // Leave the source object in a valid state
other.length = 0;
}
// Move Assignment Operator
String& operator=(String&& other) noexcept {
if (this == &other) {
return *this; // Self-assignment check
}

delete[] data; // Free existing memory

data = other.data;
length = other.length;

other.data = nullptr; // Leave the source object in a valid state


other.length = 0;

return *this;
}

void display() const {


cout << data << endl;
}
};

int main() {
String str1("Hello");
String str2 = str1; // Calls copy constructor
String str3;
str3 = str1; // Calls copy assignment operator

String str4 = std::move(str1); // Calls move constructor


String str5;
str5 = std::move(str2); // Calls move assignment operator

str3.display();
str4.display();
str5.display();

return 0;
}

25. Creating and Managing Threads in C++

In C++, threads can be created and managed using the <thread> library, which provides a
simple interface for multithreading. A thread can be created by passing a function (or callable
object) to the std::thread constructor.

Basic Example of Creating a Thread:

cpp
Copy code
#include <iostream>
#include <thread>

void printMessage() {
std::cout << "Hello from the thread!" << std::endl;
}

int main() {
std::thread t(printMessage); // Create a new thread that runs
printMessage
// Wait for the thread to finish (join)
t.join(); // Ensures the main thread waits for t to complete

std::cout << "Hello from the main thread!" << std::endl;

return 0;
}

In this example:

• A new thread t is created to execute the printMessage function.


• t.join() is used to wait for the thread to finish before the main thread continues, ensuring
proper synchronization.

Managing Threads:

• Joining (join()): The join method ensures the main thread waits for the spawned thread
to finish execution. It is important to call join to avoid program termination before threads
complete.
• Detaching (detach()): detach allows the thread to run independently, continuing its
execution even if the main thread completes. Once detached, the thread cannot be joined.

cpp
Copy code
std::thread t(printMessage);
t.detach(); // t runs independently from the main thread

26. Race Conditions and Deadlocks

Race Conditions

• A race condition occurs when two or more threads access shared data simultaneously, and
at least one thread modifies the data. The outcome of the program becomes unpredictable
because the sequence of execution by the threads is not guaranteed.

Example: If two threads are incrementing the same global counter, the final value of the
counter depends on the interleaving of thread execution, potentially leading to incorrect
results.

Preventing Race Conditions:

• Use synchronization mechanisms such as mutexes or locks to control access to shared data.
• Use atomic operations for simple operations on shared variables (e.g., std::atomic).

Deadlocks

• A deadlock occurs when two or more threads are waiting on each other to release resources,
resulting in a standstill where none of the threads can proceed.

Example: Thread A locks mutex1 and waits for mutex2, while thread B locks mutex2 and
waits for mutex1. Neither thread can proceed, causing a deadlock.
Preventing Deadlocks:

• Lock ordering: Always acquire locks in a predetermined order.


• Avoid nested locks: Minimize locking of multiple resources.
• Use std::lock: The std::lock function can lock multiple mutexes without causing a
deadlock.
• Use try-locks (std::try_lock): Attempt to acquire locks without blocking, and back off if
the locks are not available.

27. Using Mutexes and Locks in C++

A mutex (mutual exclusion) is a synchronization primitive that ensures only one thread can
access a critical section of code at a time. In C++, std::mutex is used for this purpose, with
the following common functions:

• lock(): Acquires the lock. If the mutex is already locked, it blocks until the mutex becomes
available.
• unlock(): Releases the lock, allowing other threads to acquire it.
• try_lock(): Attempts to acquire the lock without blocking. Returns immediately with a
success or failure status.

Basic Example of Using a Mutex:

cpp
Copy code
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // Global mutex


int sharedCounter = 0;

void incrementCounter() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // Locks mtx
++sharedCounter;
// mtx is automatically unlocked when lock goes out of scope
}
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

std::cout << "Final counter value: " << sharedCounter << std::endl;
return 0;
}

In the example above:

• The std::mutex (mtx) is used to protect the shared variable sharedCounter from
simultaneous modification.
• A std::lock_guard automatically locks the mutex when it is created and unlocks it when
it goes out of scope, ensuring exception safety.

std::lock_guard and std::unique_lock

• std::lock_guard:
o A simpler way to lock a mutex.
o Automatically locks the mutex on creation and unlocks it on destruction (scope-
based locking).
• std::unique_lock:
o More flexible than std::lock_guard.
o Can lock and unlock the mutex multiple times, and supports deferred locking.

Example Using std::unique_lock:

cpp
Copy code
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // Create the lock
but don't lock yet
// Perform some operations
lock.lock(); // Lock the mutex
// Critical section
lock.unlock(); // Unlock the mutex

Summary

• Creating and managing threads in C++ involves using std::thread, along with proper
synchronization techniques to avoid race conditions and deadlocks.
• Race conditions occur when multiple threads access shared resources without proper
synchronization, while deadlocks happen when threads wait indefinitely for resources locked
by each other.
• Mutexes and locks are used to control access to shared resources, ensuring that only one
thread executes a critical section at a time. Proper use of synchronization mechanisms like
std::lock_guard and std::unique_lock helps prevent these issues.

28. What Are Templates in C++? Why Are They Used?

Templates in C++ are a powerful feature that allows writing generic, type-independent code.
They enable functions and classes to operate with different data types without rewriting the
entire code for each type. Templates help achieve code reusability and type safety while
avoiding code duplication.

Templates are particularly useful in implementing generic data structures (e.g., linked lists,
stacks) and algorithms (e.g., sorting, searching), where the operations are independent of the
data type.

Example of a Template Function:

cpp
Copy code
#include <iostream>
using namespace std;
// Template function to find the maximum of two values
template <typename T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}

int main() {
cout << "Max of 3 and 7 is: " << getMax(3, 7) << endl; // Uses int
cout << "Max of 3.5 and 7.2 is: " << getMax(3.5, 7.2) << endl; // Uses
double
return 0;
}

In this example, the getMax function works for both integers and floating-point numbers
without having separate implementations for each type.

29. Difference Between Function Templates and Class Templates

• Function Templates:
o Allow functions to operate on different data types while using the same function
definition.
o The template parameter specifies the data type when the function is called.
o Syntax: template <typename T>, where T is the template parameter that can
represent any data type.

Example of a Function Template:

cpp
Copy code
template <typename T>
T add(T a, T b) {
return a + b;
}

int main() {
cout << add(3, 4) << endl; // Calls add<int>
cout << add(3.5, 4.5) << endl; // Calls add<double>
return 0;
}

• Class Templates:
o Allow the creation of generic classes that can operate with different data types.
o The template parameter is specified when an instance of the class is created.
o Useful for implementing data structures like vector, stack, queue, etc., which should
work with various data types.

Example of a Class Template:

cpp
Copy code
template <typename T>
class MyContainer {
private:
T data;
public:
MyContainer(T d) : data(d) {}
T getData() const { return data; }
};

int main() {
MyContainer<int> intContainer(10);
MyContainer<double> doubleContainer(5.5);
cout << "Integer container: " << intContainer.getData() << endl;
cout << "Double container: " << doubleContainer.getData() <<
endl;
return 0;
}

In the example, MyContainer is a class template that can store different data types such as
int or double, based on the template parameter provided.

30. How Does Template Specialization Work?

Template specialization allows customizing the behavior of a template for a specific type. It
is used when the general implementation of a template does not meet the needs for certain
data types, allowing for specific code to be written for those types.

Full Template Specialization

In full template specialization, a template is fully specialized for a specific data type.

Example of Full Template Specialization:

cpp
Copy code
#include <iostream>
using namespace std;

// Generic template
template <typename T>
class Printer {
public:
void print(const T& data) {
cout << "Generic printer: " << data << endl;
}
};

// Full specialization for type int


template <>
class Printer<int> {
public:
void print(const int& data) {
cout << "Integer printer: " << data << endl;
}
};

int main() {
Printer<double> doublePrinter;
Printer<int> intPrinter;

doublePrinter.print(5.5); // Uses the generic template


intPrinter.print(10); // Uses the specialized template for int
return 0;
}

In this example, the Printer class is fully specialized for int, allowing a different
implementation for integer printing compared to other types.

Partial Template Specialization

Partial template specialization allows customizing templates for certain combinations of


template parameters while keeping others generic. This is mainly used with class templates,
as function templates do not support partial specialization.

31.Example of Partial Template Specialization:

cpp
Copy code
#include <iostream>
using namespace std;

// Generic template
template <typename T, typename U>
class Pair {
public:
T first;
U second;
Pair(T f, U s) : first(f), second(s) {}
void display() const {
cout << "Pair: " << first << ", " << second << endl;
}
};

// Partial specialization for the case where both types are the same
template <typename T>
class Pair<T, T> {
public:
T first;
T second;
Pair(T f, T s) : first(f), second(s) {}
void display() const {
cout << "Specialized Pair (same type): " << first << ", " << second
<< endl;
}
};

int main() {
Pair<int, double> mixedPair(3, 4.5);
Pair<int, int> intPair(5, 10);

mixedPair.display(); // Uses the generic template


intPair.display(); // Uses the partial specialization

return 0;
}

In this example, the Pair class template is partially specialized for cases where both temp
32. Reading from and Writing to Files in C++

In C++, file handling is done using the fstream library, which provides functionalities to
read from and write to files. The primary classes used for file handling are:

• std::ifstream: For reading from files.


• std::ofstream: For writing to files.
• std::fstream: For both reading and writing.

Example: Writing to a File

Here’s how you can write data to a file using std::ofstream:

cpp
Copy code
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
ofstream outFile("example.txt"); // Open a file for writing
if (!outFile) {
cerr << "Error opening file for writing." << endl;
return 1;
}

outFile << "Hello, World!" << endl; // Write to the file


outFile << "This is a test file." << endl;

outFile.close(); // Close the file


cout << "Data written to file successfully." << endl;

return 0;
}

In this example:

• An output file stream (ofstream) is created, which opens "example.txt" for writing.
• Data is written to the file using the stream insertion operator (<<).
• The file is closed using the close() method.

Example: Reading from a File

Here’s how to read data from a file using std::ifstream:

cpp
Copy code
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
ifstream inFile("example.txt"); // Open a file for reading
if (!inFile) {
cerr << "Error opening file for reading." << endl;
return 1;
}

string line;
while (getline(inFile, line)) { // Read line by line
cout << line << endl; // Output to the console
}

inFile.close(); // Close the file


return 0;
}

In this example:

• An input file stream (ifstream) is created to open "example.txt" for reading.


• The getline() function is used to read the file line by line until the end of the file is
reached.
• Each line is printed to the console.

33. Different Modes for Opening a File

When opening a file in C++, you can specify different modes that dictate how the file will be
accessed. Here are the common modes:

• ios::in: Open the file for reading. The file must exist; otherwise, the open operation
fails.
• ios::out: Open the file for writing. If the file already exists, it is truncated (emptied).
If the file does not exist, it is created.
• ios::app: Open the file for appending. Data is written to the end of the file,
preserving existing data. The file is created if it does not exist.
• ios::ate: Open the file and move the read/write control to the end of the file. This
can be useful for seeking and writing data at the end without truncating the file.
• ios::trunc: If the file is opened for output (ios::out), this mode truncates the file
to zero length, effectively clearing its contents.
• ios::binary: Open the file in binary mode rather than text mode. This mode is
useful for reading or writing non-text files (e.g., images, executables).

Combining Modes

You can combine multiple modes using the bitwise OR operator (|). For example:

cpp
Copy code
std::ofstream outFile("example.txt", ios::out | ios::app | ios::binary);

In this example, the file "example.txt" is opened for appending (ios::app), writing
(ios::out), and in binary mode (ios::binary).

Summary
• Reading and writing to files in C++ is done using the fstream library with ifstream for
reading and ofstream for writing.
• Different file opening modes include ios::in, ios::out, ios::app, ios::ate,
ios::trunc,

You might also like