← Back to C++ Course | Chapter 12: Exception Handling | Lesson 4 of 5

C++ Custom Exceptions

Why Custom Exceptions?

The exceptions built into the standard library are useful but generic; defining your own exception types lets you create error categories that map precisely onto your application's own business logic, which makes debugging and targeted error handling far cleaner.

Example: Why Custom Exceptions?

cpp
#include <iostream>
#include <string>

class InvalidAgeError {
public:
	std::string message = "Age cannot be negative";
};

int main() {
	try {
		throw InvalidAgeError();
	} catch (InvalidAgeError &e) {
		std::cout << e.message << std::endl;
	}
	return 0;
}

Creating a Basic Exception Class

At its simplest, a custom exception class is just an ordinary class with whatever data and methods you decide it needs — you throw instances of it and catch them by that specific class type, just like any standard exception.

Example: Creating a Basic Exception Class

cpp
#include <iostream>
#include <string>

class MyException {
public:
	std::string message;
	MyException(std::string msg) : message(msg) {}
};

int main() {
	try {
		throw MyException("Something broke");
	} catch (MyException &e) {
		std::cout << e.message << std::endl;
	}
	return 0;
}

Inheriting from std::exception

The professional way to build a custom exception is to inherit from std::exception and override its virtual what() method. Doing so lets generic catch blocks written for std::exception handle your custom exceptions too, without needing to know about your specific type.

Example: Inheriting from std::exception

cpp
#include <iostream>
#include <exception>

class MyException : public std::exception {
public:
	const char *what() const noexcept override {
		return "Custom exception occurred";
	}
};

int main() {
	try {
		throw MyException();
	} catch (std::exception &e) {
		std::cout << e.what() << std::endl;
	}
	return 0;
}

Custom Exceptions with Custom Data

Because your exception is a real class, it can carry custom member data along with the failure — an error code, a file path, a timestamp — giving the code that catches it far richer information than a bare error message alone.

Example: Custom Exceptions with Custom Data

cpp
#include <iostream>
#include <exception>

class ValidationError : public std::exception {
public:
	int errorCode;
	ValidationError(int code) : errorCode(code) {}
	const char *what() const noexcept override {
		return "Validation failed";
	}
};

int main() {
	try {
		throw ValidationError(422);
	} catch (ValidationError &e) {
		std::cout << e.what() << " code=" << e.errorCode << std::endl;
	}
	return 0;
}

Practical Usage in Validation

In practice, custom exceptions are typically thrown from inside validation logic or system-level operations, where they communicate exactly what went wrong to whatever code further up the stack is responsible for deciding how to respond.

Example: Practical Usage in Validation

cpp
#include <iostream>
#include <exception>

class InvalidAgeError : public std::exception {
public:
	const char *what() const noexcept override {
		return "Age must be non-negative";
	}
};

void checkAge(int age) {
	if (age < 0) throw InvalidAgeError();
}

int main() {
	try {
		checkAge(-5);
	} catch (std::exception &e) {
		std::cout << e.what() << std::endl;
	}
	return 0;
}
🔒

Chapter Quiz — Complete all 5 topics to unlock

0/5 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.