Questions from One Job Interview on C++ Developer

author image Hirely
at 06 Jan, 2025

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:

  1. throw: Used to signal that an exception has occurred.
  2. try: A block of code that monitors for exceptions.
  3. 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 the try block.
  • You can have multiple catch blocks to handle different types of exceptions, and each catch 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 as std::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 from std::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:

  1. 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
    }
  2. 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.

  3. Custom exceptions: You can define your own exceptions by inheriting from std::exception or one of its derived classes, and overriding the what() 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 function operator+ takes a reference to another Complex object and returns a new Complex object.
  • You can now use the + operator to add two Complex objects, as though they were built-in types.

Rules for Operator Overloading:

  1. Operators can only be overloaded for user-defined types (i.e., classes or structs), not for built-in types (like int, float, etc.).
  2. 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)
  3. 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:

  1. Arithmetic Operators: +, -, *, /, %
  2. Relational Operators: ==, !=, <, >, <=, >=
  3. Logical Operators: &&, ||
  4. Bitwise Operators: &, |, ^, ~, <<, >>
  5. Increment/Decrement Operators: ++, -- (both prefix and postfix)
  6. Assignment Operator: =
  7. Subscript Operator: []
  8. Function Call Operator: ()
  9. 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:

  1. 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).
  2. 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.
  3. 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:

  1. Improved Readability: Overloading operators makes your code look more intuitive and natural. For instance, adding two complex numbers with + feels more natural than calling an add() function.
  2. 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.

Related Posts

Trace Job opportunities

Hirely, your exclusive interview companion, empowers your competence and facilitates your interviews.

Get Started Now