Skip to content

C++ Best Practices

Overview

Following best practices helps write clean, efficient, and maintainable C++ code.

Code Style and Organization

Naming Conventions

cpp
// Use meaningful names
class StudentDatabase {
private:
    std::vector<Student> students_;
    int max_capacity_;
    
public:
    void addStudent(const Student& student);
    bool isFull() const;
};

// Use snake_case for variables and functions
int calculate_average(const std::vector<int>& numbers);
std::string user_name;

// Use PascalCase for classes and structs
class FileManager;
struct Point2D;

// Use UPPER_CASE for constants
const int MAX_BUFFER_SIZE = 1024;
const double PI = 3.14159;

Memory Management

Prefer Smart Pointers

cpp
#include <memory>
#include <vector>

class ResourceHandler {
public:
    // Good: Use smart pointers
    std::unique_ptr<Resource> createResource() {
        return std::make_unique<Resource>();
    }
    
    // Bad: Raw pointers with manual management
    Resource* createResourceBad() {
        Resource* ptr = new Resource();
        // Easy to forget delete!
        return ptr;
    }
};

// Use RAII principle
class FileHandler {
private:
    std::ofstream file_;
    
public:
    FileHandler(const std::string& filename) : file_(filename) {
        if (!file_.is_open()) {
            throw std::runtime_error("Cannot open file");
        }
    }
    
    ~FileHandler() {
        if (file_.is_open()) {
            file_.close();
        }
    }
    
    void write(const std::string& data) {
        file_ << data;
    }
};

Error Handling

Use Exceptions Appropriately

cpp
#include <stdexcept>

class Calculator {
public:
    double divide(double a, double b) {
        if (b == 0.0) {
            throw std::invalid_argument("Division by zero");
        }
        return a / b;
    }
    
    // Use noexcept for functions that don't throw
    int add(int a, int b) noexcept {
        return a + b;
    }
};

int main() {
    Calculator calc;
    
    try {
        double result = calc.divide(10.0, 0.0);
    } catch (const std::invalid_argument& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }
    
    return 0;
}

Performance Optimization

Use const and References

cpp
class DataProcessor {
public:
    // Good: Pass by const reference
    void processData(const std::vector<int>& data) const {
        // Process data without copying
    }
    
    // Bad: Pass by value (creates copy)
    void processDataBad(std::vector<int> data) {
        // Unnecessary copy
    }
    
    // Return by value when appropriate (NRVO/move semantics)
    std::vector<int> getProcessedData() const {
        std::vector<int> result;
        // Process and return
        return result;  // Move semantics will be used
    }
};

Modern C++ Features

Use Modern C++ Constructs

cpp
#include <memory>
#include <vector>
#include <algorithm>

// Use auto for type inference
void processContainer(const std::vector<int>& container) {
    for (const auto& item : container) {
        // Process item
    }
}

// Use range-based for loops
void printData(const std::vector<std::string>& data) {
    for (const auto& str : data) {
        std::cout << str << std::endl;
    }
}

// Use lambda expressions
void filterData(std::vector<int>& data) {
    data.erase(
        std::remove_if(data.begin(), data.end(), 
                    [](int x) { return x < 0; }),
        data.end()
    );
}

// Use constexpr for compile-time constants
constexpr int BUFFER_SIZE = 1024;
constexpr double PI = 3.14159265359;

Thread Safety

Proper Synchronization

cpp
#include <mutex>
#include <atomic>

class ThreadSafeCounter {
private:
    std::mutex mutex_;
    std::atomic<int> atomic_counter_{0};
    int counter_ = 0;
    
public:
    // Using mutex
    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++counter_;
    }
    
    int getValue() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return counter_;
    }
    
    // Using atomic for simple operations
    void atomicIncrement() {
        ++atomic_counter_;
    }
    
    int getAtomicValue() const {
        return atomic_counter_.load();
    }
};

Design Patterns

Follow SOLID Principles

cpp
// Single Responsibility Principle
class Logger {
public:
    void log(const std::string& message);
};

class DataProcessor {
public:
    void process(const Data& data);
};

// Open/Closed Principle
class Shape {
public:
    virtual double area() const = 0;
    virtual void draw() const = 0;
};

class Circle : public Shape {
public:
    double area() const override;
    void draw() const override;
};

// Dependency Inversion Principle
class INotificationService {
public:
    virtual void send(const std::string& message) = 0;
};

class EmailService : public INotificationService {
public:
    void send(const std::string& message) override;
};

class UserManager {
private:
    std::unique_ptr<INotificationService> notifier_;
    
public:
    UserManager(std::unique_ptr<INotificationService> notifier)
        : notifier_(std::move(notifier)) {}
};

Content is for learning and research only.