Phone Number Class in C++: Implementation Guide
This article provides a detailed guide to implementing a PhoneNumber
class in C++. We’ll cover design considerations, member variables, constructors, methods (including validation, formatting, and comparison), and error handling. The code examples will be comprehensive, demonstrating best practices.
1. Design Considerations:
Before diving into the code, consider these key design aspects:
- Data Representation: How will we store the phone number components? A single string is tempting, but separate components (country code, area code, local number) offer flexibility for formatting and validation.
- Validation: Phone number formats vary globally. We need a robust validation mechanism. We’ll implement a basic form of validation (for a US-based number, to simplify the initial example).
- Formatting: We need to be able to output the phone number in various formats (e.g., (XXX) XXX-XXXX, XXX-XXX-XXXX, +1XXXXXXXXXX).
- Comparison: We should be able to compare two
PhoneNumber
objects for equality. - Error Handling: How will we handle invalid phone numbers? We’ll use exceptions.
- Immutability (Optional): Consider making the
PhoneNumber
class immutable after construction. This simplifies reasoning about the object and prevents accidental modification. We’ll implement mutability (getters/setters) to demonstrate a broader range of techniques, but immutability is often preferable in practice.
2. Member Variables:
We’ll use the following member variables:
m_countryCode
:std::string
(e.g., “1” for the US). Using a string allows for international country codes.m_areaCode
:std::string
(e.g., “555”).m_localNumber
:std::string
(e.g., “1234567”).m_extension
:std::string
(optional extension).
3. Header File (PhoneNumber.h):
“`c++
ifndef PHONE_NUMBER_H
define PHONE_NUMBER_H
include
include
class PhoneNumber {
public:
// Constructors
PhoneNumber(const std::string& countryCode, const std::string& areaCode, const std::string& localNumber, const std::string& extension = “”);
PhoneNumber(const std::string& fullPhoneNumber); // Parse from a full number string
PhoneNumber() = default; // Default constructor (creates an “empty” phone number)
// Getters
std::string getCountryCode() const { return m_countryCode; }
std::string getAreaCode() const { return m_areaCode; }
std::string getLocalNumber() const { return m_localNumber; }
std::string getExtension() const {return m_extension; }
// Setters
void setCountryCode(const std::string& countryCode);
void setAreaCode(const std::string& areaCode);
void setLocalNumber(const std::string& localNumber);
void setExtension(const std::string& extension);
// Formatting
std::string format(const std::string& formatSpecifier = "(XXX) XXX-XXXX") const;
// Comparison
bool operator==(const PhoneNumber& other) const;
bool operator!=(const PhoneNumber& other) const;
// Validation (Static method)
static bool isValidPhoneNumber(const std::string& fullPhoneNumber);
private:
std::string m_countryCode;
std::string m_areaCode;
std::string m_localNumber;
std::string m_extension;
// Helper functions (private)
bool isValidUSNumber() const;
std::string cleanNumber(const std::string& number) const;
};
// Exception class for invalid phone numbers
class InvalidPhoneNumberException : public std::runtime_error {
public:
InvalidPhoneNumberException(const std::string& message) : std::runtime_error(message) {}
};
endif
“`
4. Implementation File (PhoneNumber.cpp):
“`c++
include “PhoneNumber.h”
include
include // For std::remove_if
PhoneNumber::PhoneNumber(const std::string& countryCode, const std::string& areaCode, const std::string& localNumber, const std::string& extension) :
m_countryCode(countryCode), m_areaCode(areaCode), m_localNumber(localNumber), m_extension(extension)
{
if (!isValidUSNumber()) {
throw InvalidPhoneNumberException(“Invalid phone number components.”);
}
}
PhoneNumber::PhoneNumber(const std::string& fullPhoneNumber) {
std::string cleanedNumber = cleanNumber(fullPhoneNumber);
if (!isValidPhoneNumber(cleanedNumber))
{
throw InvalidPhoneNumberException(“Invalid phone number format: ” + fullPhoneNumber);
}
// Basic US-centric parsing (adjust for international formats as needed)
if (cleanedNumber.length() == 10) {
m_countryCode = “1”; // Default to US country code
m_areaCode = cleanedNumber.substr(0, 3);
m_localNumber = cleanedNumber.substr(3);
m_extension = “”;
}
else if (cleanedNumber.length() == 11 && cleanedNumber[0] == ‘1’) {
m_countryCode = “1”;
m_areaCode = cleanedNumber.substr(1, 3);
m_localNumber = cleanedNumber.substr(4);
m_extension = “”;
}
else if (cleanedNumber.length() > 11)
{
size_t first_ten = cleanedNumber.length()-10;
m_countryCode = cleanedNumber.substr(0,first_ten);
m_areaCode = cleanedNumber.substr(first_ten, 3);
m_localNumber = cleanedNumber.substr(first_ten+3,7);
if (cleanedNumber.length() > first_ten + 10)
{
m_extension = cleanedNumber.substr(first_ten + 10);
}
else {
m_extension = "";
}
}
else {
throw InvalidPhoneNumberException(“Invalid phone number length: ” + fullPhoneNumber);
}
}
void PhoneNumber::setCountryCode(const std::string& countryCode) {
m_countryCode = countryCode;
if (!isValidUSNumber()) {
throw InvalidPhoneNumberException(“Invalid phone number components (after setting country code).”);
}
}
void PhoneNumber::setAreaCode(const std::string& areaCode) {
m_areaCode = areaCode;
if (!isValidUSNumber()) {
throw InvalidPhoneNumberException(“Invalid phone number components (after setting area code).”);
}
}
void PhoneNumber::setLocalNumber(const std::string& localNumber) {
m_localNumber = localNumber;
if (!isValidUSNumber()) {
throw InvalidPhoneNumberException(“Invalid phone number components (after setting local number).”);
}
}
void PhoneNumber::setExtension(const std::string &extension)
{
m_extension = extension;
}
std::string PhoneNumber::format(const std::string& formatSpecifier) const {
std::string formattedNumber = formatSpecifier;
formattedNumber.replace(formattedNumber.find(“XXX”), 3, m_areaCode);
formattedNumber.replace(formattedNumber.find(“XXX”), 3, m_localNumber.substr(0, 3));
formattedNumber.replace(formattedNumber.find(“XXXX”), 4, m_localNumber.substr(3));
if (!m_countryCode.empty() && m_countryCode != "1")
{
//Prepend if it is not empty and not 1.
formattedNumber = "+" + m_countryCode + " " + formattedNumber;
}
if(!m_extension.empty())
{
formattedNumber += " ext. " + m_extension;
}
return formattedNumber;
}
bool PhoneNumber::operator==(const PhoneNumber& other) const {
return (m_countryCode == other.m_countryCode &&
m_areaCode == other.m_areaCode &&
m_localNumber == other.m_localNumber &&
m_extension == other.m_extension);
}
bool PhoneNumber::operator!=(const PhoneNumber& other) const {
return !(*this == other);
}
bool PhoneNumber::isValidPhoneNumber(const std::string& fullPhoneNumber) {
std::string cleanedNumber = PhoneNumber::cleanNumber(fullPhoneNumber);
// Regular expression for a basic US phone number (10 or 11 digits, with the first digit being 1 if 11 digits)
std::regex usPhoneRegex(R"(^(?:1)?(\d{10})$)"); // 10 or 11 digits, optional leading 1
std::regex internationalRegex(R"(^\d{1,4}\d{10}\d*$)");
return std::regex_match(cleanedNumber, usPhoneRegex) || std::regex_match(cleanedNumber, internationalRegex);
}
bool PhoneNumber::isValidUSNumber() const {
// Basic US-centric validation (must have area code and local number)
if (m_areaCode.length() != 3 || m_localNumber.length() != 7) {
return false;
}
// Check if they contain only digits
for (char c : m_areaCode) {
if (!isdigit(c)) return false;
}
for (char c : m_localNumber) {
if (!isdigit(c)) return false;
}
return true;
}
std::string PhoneNumber::cleanNumber(const std::string& number) const {
std::string cleaned = number;
// Remove non-digit characters
cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), { return !isdigit(c); }), cleaned.end());
return cleaned;
}
“`
5. Explanation and Usage:
- Constructors: We provide three constructors:
- One takes individual components (country code, area code, local number, extension).
- One takes a full phone number string and attempts to parse it.
- A default constructor.
- Getters and Setters: Provide access to and modification of the member variables. The setters include validation, throwing an exception if the resulting number is invalid.
format()
Method: Formats the phone number according to the providedformatSpecifier
. The default format is(XXX) XXX-XXXX
. This method handles both US and international numbers, and includes extensions.- Comparison Operators:
operator==
andoperator!=
allow for easy comparison ofPhoneNumber
objects. isValidPhoneNumber()
(Static Method): A static method to validate a full phone number string using a regular expression. This provides a way to validate a phone number before creating aPhoneNumber
object.isValidUSNumber()
(Private Method): A private helper function to validate the individual components of a US-style phone number.cleanNumber()
(Private Method): Removes non-digit characters from the input string. This simplifies validation and parsing.- Exception Handling: The
InvalidPhoneNumberException
is thrown when an invalid phone number is encountered during construction or when setting values.
Example Usage:
“`c++
include “PhoneNumber.h”
include
int main() {
try {
PhoneNumber num1(“1”, “555”, “1234567”);
std::cout << “Number 1: ” << num1.format() << std::endl; // Output: Number 1: (555) 123-4567
PhoneNumber num2("+15551234567");
std::cout << "Number 2: " << num2.format() << std::endl; // Output: Number 2: (555) 123-4567
PhoneNumber num3("44", "20", "79460000", "123"); //UK Number
std::cout << "Number 3: " << num3.format() << std::endl; // Output: Number 3: +44 (20) 794-6000 ext. 123
PhoneNumber num4("1-800-555-1212 ext. 55");
std::cout << "Number 4: " << num4.format() << std::endl;
PhoneNumber num5("800555121255"); //no country code
std::cout << "Number 5: " << num5.format() << std::endl;
if (num1 == num2) {
std::cout << "num1 and num2 are equal." << std::endl;
} else {
std::cout << "num1 and num2 are not equal." << std::endl;
}
// Example of setting and getting individual components
num1.setAreaCode("999");
std::cout << "Modified Number 1: " << num1.format() << std::endl; // Output: Modified Number 1: (999) 123-4567
// Example of invalid number (will throw an exception)
// PhoneNumber invalidNum("abc", "def", "ghi");
PhoneNumber invalidNum2("1-800-555-abc");
} catch (const InvalidPhoneNumberException& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
try {
PhoneNumber empty; // Test default constructor.
std::cout << "Empty phone: cc=" << empty.getCountryCode() << "; ac=" << empty.getAreaCode() << "; ln=" << empty.getLocalNumber() << "; ex="<< empty.getExtension() <<std::endl;
} catch (const InvalidPhoneNumberException& e) {
std::cerr << "Error with empty constructor: " << e.what() << std::endl;
}
return 0;
}
“`
6. Further Enhancements:
- Internationalization: Implement more robust validation for international phone numbers, potentially using a library like libphonenumber.
- Libphonenumber Integration: Consider using the Google libphonenumber library for comprehensive validation, formatting, and parsing. This library is the industry standard for handling phone numbers. Integrating it would significantly enhance the robustness and accuracy of the class.
- Region-Specific Formatting: Allow for different formatting rules based on the country code.
- Database Integration: Add functionality to store and retrieve phone numbers from a database.
- Operator Overloading (<<): Overload the
<<
operator to simplify output to streams.
This comprehensive guide provides a solid foundation for implementing a PhoneNumber
class in C++. By following these principles and incorporating the suggested enhancements, you can create a robust and versatile class for handling phone numbers in your applications. Remember to choose the validation and formatting methods that best suit your specific needs and the level of internationalization required. Using libphonenumber
is highly recommended for production environments.