Questions from One Job Interview on C++ Developer
Question: What is exception handling in C++?
Answer:
Exception handling in C++ is a mechanism that allows a program to detect and respond to runtime errors or exceptional conditions (exceptions) in a controlled way. It involves using special keywords and constructs to catch and handle errors, allowing the program to recover from them or at least terminate gracefully, rather than crashing or behaving unpredictably.
C++ provides three main components for exception handling:
- throw: Used to signal that an exception has occurred.
- try: A block of code that monitors for exceptions.
- catch: A block of code that handles the exception after it is thrown.
Basic Syntax:
try {
// Code that might throw an exception
throw exception_type; // Throw an exception
}
catch (exception_type& e) {
// Handle the exception
std::cout << "Exception caught: " << e.what() << std::endl;
}
1. throw:
- The
throw
keyword is used to throw an exception when an error or an exceptional condition occurs. - It can throw a wide variety of objects, not just error messages. For example, you can throw integers, strings, or user-defined objects.
Example:
throw 5; // Throws an integer
throw "Error message"; // Throws a string
throw std::runtime_error("Something went wrong!"); // Throws a runtime error exception
2. try:
- The
try
block is used to enclose the code that may potentially throw an exception. - If an exception is thrown, control is transferred to the corresponding
catch
block.
Example:
try {
int a = 10;
int b = 0;
if (b == 0) {
throw std::logic_error("Division by zero is not allowed");
}
int c = a / b;
}
3. catch:
- The
catch
block handles exceptions thrown from thetry
block. - You can have multiple
catch
blocks to handle different types of exceptions, and eachcatch
block must specify the type of exception it handles.
Example:
try {
// Code that may throw an exception
throw std::invalid_argument("Invalid argument");
}
catch (const std::invalid_argument& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cout << "Caught general exception: " << e.what() << std::endl;
}
Types of Exceptions:
- Standard exceptions: C++ provides a set of standard exceptions in the
<stdexcept>
library, such asstd::exception
,std::invalid_argument
,std::out_of_range
,std::runtime_error
, etc. - User-defined exceptions: You can define your own exception classes by inheriting from the standard
std::exception
class or directly fromstd::runtime_error
.
Example of user-defined exception:
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "My custom exception occurred";
}
};
try {
throw MyException();
}
catch (const MyException& e) {
std::cout << "Caught custom exception: " << e.what() << std::endl;
}
Exception Propagation:
- When an exception is thrown in a function, it propagates (or “bubbles up”) to the calling function until it is caught by a
catch
block. - If no
catch
block handles the exception, the program terminates.
Example of exception propagation:
void functionA() {
throw std::runtime_error("Error in functionA");
}
void functionB() {
functionA(); // Propagates the exception to functionB
}
int main() {
try {
functionB(); // Catches the exception from functionA
}
catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
Exception Handling in C++ Standard Library:
- The C++ Standard Library provides many exceptions such as
std::out_of_range
,std::logic_error
,std::runtime_error
,std::bad_alloc
, and more. - These exceptions are used to signal various errors that occur during the execution of C++ programs.
Key Points:
-
Exception Specification: C++ allows a function to declare which exceptions it may throw using the
noexcept
keyword or by using a specific exception specification (though the latter is now deprecated).void func() noexcept { // No exceptions expected // Function code }
-
Stack unwinding: When an exception is thrown, the stack is unwound, which means all objects in scope are destroyed, and destructors are called, ensuring proper cleanup.
-
Custom exceptions: You can define your own exceptions by inheriting from
std::exception
or one of its derived classes, and overriding thewhat()
method to provide a message.
Advantages of Exception Handling:
- Error Propagation: Errors can be propagated through multiple layers of function calls, allowing you to handle them at a higher level if needed.
- Separation of Concerns: Code for handling errors is separated from the normal logic, improving readability and maintainability.
- Automatic Cleanup: Objects are destructed automatically when an exception is thrown, allowing for more predictable resource management (especially when using RAII principles).
Conclusion:
Exception handling in C++ is a powerful feature that allows developers to write cleaner, more robust code. By using try
, throw
, and catch
, you can handle runtime errors gracefully, manage resources effectively, and ensure that your program can recover from unexpected conditions without crashing.
Question: What is operator overloading in C++?
Answer:
Operator overloading in C++ allows you to define or modify the behavior of operators (such as +
, -
, *
, /
, etc.) for user-defined types (classes and structures). This provides a way to perform operations on objects of these types in a natural and intuitive way, similar to how operators work with built-in types like int
and float
.
Operator overloading enables custom classes to behave like built-in types when using operators. For example, you can add two objects of a class using the +
operator if you overload it for that class.
Syntax for Operator Overloading:
To overload an operator, you need to define a function that represents how the operator works. The function’s name will be the keyword operator
followed by the operator being overloaded.
return_type operator operator_symbol (parameters)
{
// Define the operator logic here
}
Example of Operator Overloading:
Here’s an example of how you might overload the +
operator for a Complex
class, where we define how two complex numbers are added together.
Step 1: Define the Class
#include <iostream>
using namespace std;
class Complex {
private:
int real, imag;
public:
// Constructor
Complex(int r, int i) : real(r), imag(i) {}
// Overload the + operator
Complex operator+(const Complex& obj) {
return Complex(real + obj.real, imag + obj.imag);
}
// Display function
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
Step 2: Use the Overloaded Operator
int main() {
Complex c1(3, 4);
Complex c2(1, 2);
Complex c3 = c1 + c2; // Uses overloaded + operator
c3.display(); // Output: 4 + 6i
return 0;
}
In this example:
- The
+
operator is overloaded to add two complex numbers. The functionoperator+
takes a reference to anotherComplex
object and returns a newComplex
object. - You can now use the
+
operator to add twoComplex
objects, as though they were built-in types.
Rules for Operator Overloading:
- Operators can only be overloaded for user-defined types (i.e., classes or structs), not for built-in types (like
int
,float
, etc.). - Cannot overload certain operators: Some operators cannot be overloaded, such as:
::
(scope resolution operator).
(member access operator).*
(pointer-to-member operator)?:
(ternary conditional operator)sizeof
(size of operator)typeid
(RTTI operator)
- Cannot change the precedence or associativity of operators. The precedence (the order of operations) and associativity (left-to-right or right-to-left) of operators are fixed by C++ and cannot be changed.
Types of Operators That Can Be Overloaded:
- Arithmetic Operators:
+
,-
,*
,/
,%
- Relational Operators:
==
,!=
,<
,>
,<=
,>=
- Logical Operators:
&&
,||
- Bitwise Operators:
&
,|
,^
,~
,<<
,>>
- Increment/Decrement Operators:
++
,--
(both prefix and postfix) - Assignment Operator:
=
- Subscript Operator:
[]
- Function Call Operator:
()
- Type Conversion Operator:
type()
Example: Overloading the +=
Operator
Here’s another example, showing how you can overload the +=
operator to add the value of one object to another.
class Complex {
private:
int real, imag;
public:
Complex(int r, int i) : real(r), imag(i) {}
// Overloading the += operator
Complex& operator+=(const Complex& obj) {
real += obj.real;
imag += obj.imag;
return *this; // Return the updated object
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(3, 4);
Complex c2(1, 2);
c1 += c2; // Uses overloaded += operator
c1.display(); // Output: 4 + 6i
return 0;
}
Important Considerations:
- Return Type: When overloading assignment operators or compound operators (like
+=
,-=
, etc.), you usually return the object itself (by reference), as this allows chaining operations (e.g.,a += b += c
). - Const-Correctness: If the operator doesn’t modify the operands, it’s a good practice to pass them as
const
references to avoid unnecessary copying. - Operator Overloading for Symmetry: For operators like
+
,-
,*
, etc., it’s common to overload both the operator itself and its corresponding assignment operator (e.g.,+=
,-=
).
Example: Overloading the <<
Operator for Output
You can also overload the <<
operator to output a custom class object in a more readable format.
class Complex {
private:
int real, imag;
public:
Complex(int r, int i) : real(r), imag(i) {}
// Overloading the << operator for output
friend ostream& operator<<(ostream& os, const Complex& obj) {
os << obj.real << " + " << obj.imag << "i";
return os;
}
};
int main() {
Complex c1(3, 4);
cout << c1 << endl; // Output: 3 + 4i
return 0;
}
Advantages of Operator Overloading:
- Improved Readability: Overloading operators makes your code look more intuitive and natural. For instance, adding two complex numbers with
+
feels more natural than calling anadd()
function. - Better Code Design: It enables you to design better abstractions for your classes and provide familiar interfaces for users of your class.
Conclusion:
Operator overloading in C++ provides a powerful way to define how operators work with user-defined types. This enhances the readability and usability of your classes, making them behave in a way that is more intuitive and closer to how built-in types are used in the language. However, it’s important to use operator overloading judiciously to avoid making your code complex and difficult to maintain.
Read More
If you can’t get enough from this article, Aihirely has plenty more related information, such as C++ interview questions, C++ interview experiences, and details about various C++ job positions. Click here to check it out.
Tags
- C++
- C++ interview questions
- C++ constructors
- Inline keyword
- Reference in C++
- Class and object in C++
- Call by value vs call by reference
- Abstract class
- Virtual functions
- New vs malloc
- This pointer in C++
- C++ templates
- C++ struct vs class
- Exception handling in C++
- Operator overloading in C++
- Polymorphism in C++
- C++ pointers
- C++ iterators
- C++ STL
- Scope resolution operator
- C++ memory management
- C++ exception handling
- C++ overloading
- C++ function overriding