Modern C++ Rules

Modern C++ rules promote the use of contemporary C++ features that improve code safety, performance, and readability.

Missing noexcept

MODERN_MISSING_NOEXCEPT

Suggests adding noexcept to functions that should not throw exceptions, particularly destructors, move operations, swap functions, and simple getters/setters.

Examples:

✅ Correct
❌ Incorrect
class ResourceManager {
public:
    // Destructors should be noexcept
    ~ResourceManager() noexcept {
        cleanup();
    }
    
    // Move constructor should be noexcept
    ResourceManager(ResourceManager&& other) noexcept 
        : data_(std::move(other.data_)) {
    }
    
    // Move assignment should be noexcept
    ResourceManager& operator=(ResourceManager&& other) noexcept {
        if (this != &other) {
            data_ = std::move(other.data_);
        }
        return *this;
    }
    
    // Simple getters should be noexcept
    size_t GetSize() const noexcept {
        return data_.size();
    }
    
    // Swap functions should be noexcept
    void Swap(ResourceManager& other) noexcept {
        data_.swap(other.data_);
    }
    
private:
    std::vector<int> data_;
};
class ResourceManager {
public:
    // Missing noexcept on destructor
    ~ResourceManager() {
        cleanup();
    }
    
    // Missing noexcept on move constructor
    ResourceManager(ResourceManager&& other) 
        : data_(std::move(other.data_)) {
    }
    
    // Missing noexcept on move assignment
    ResourceManager& operator=(ResourceManager&& other) {
        if (this != &other) {
            data_ = std::move(other.data_);
        }
        return *this;
    }
    
    // Missing noexcept on simple getter
    size_t GetSize() const {
        return data_.size();
    }
    
    // Missing noexcept on swap function
    void Swap(ResourceManager& other) {
        data_.swap(other.data_);
    }
    
private:
    std::vector<int> data_;
};

Missing const

MODERN_MISSING_CONST

Detects methods that should be marked const but aren't, particularly getter methods that don't modify object state.

Examples:

✅ Correct
❌ Incorrect
class DataContainer {
public:
    // Getters are const - don't modify state
    const std::string& GetName() const {
        return name_;
    }
    
    size_t GetSize() const {
        return data_.size();
    }
    
    bool IsEmpty() const {
        return data_.empty();
    }
    
    // Query methods are const
    bool Contains(int value) const {
        return std::find(data_.begin(), data_.end(), value) != data_.end();
    }
    
    // Status checking methods are const
    bool IsValid() const {
        return !name_.empty() && !data_.empty();
    }
    
    // Non-const methods that modify state
    void SetName(const std::string& name) {
        name_ = name;
    }
    
    void AddData(int value) {
        data_.push_back(value);
    }
    
private:
    std::string name_;
    std::vector<int> data_;
};
class DataContainer {
public:
    // Missing const on getters
    const std::string& GetName() {
        return name_;
    }
    
    size_t GetSize() {
        return data_.size();
    }
    
    bool IsEmpty() {
        return data_.empty();
    }
    
    // Missing const on query methods
    bool Contains(int value) {
        return std::find(data_.begin(), data_.end(), value) != data_.end();
    }
    
    // Missing const on status checking
    bool IsValid() {
        return !name_.empty() && !data_.empty();
    }
    
    // Non-const methods that modify state
    void SetName(const std::string& name) {
        name_ = name;
    }
    
    void AddData(int value) {
        data_.push_back(value);
    }
    
private:
    std::string name_;
    std::vector<int> data_;
};

Missing [[nodiscard]]

MODERN_NODISCARD_MISSING

Suggests adding [[nodiscard]] to functions whose return values should not be ignored, including factory functions, getters, validation functions, and computational functions.

Examples:

✅ Correct
❌ Incorrect
class ResourceFactory {
public:
    // Factory functions should be [[nodiscard]]
    [[nodiscard]] std::unique_ptr<Resource> CreateResource() {
        return std::make_unique<Resource>();
    }
    
    [[nodiscard]] Resource MakeResource(int id) {
        return Resource{id};
    }
    
    // Getters should be [[nodiscard]]
    [[nodiscard]] const std::string& GetName() const {
        return name_;
    }
    
    [[nodiscard]] size_t GetCount() const {
        return count_;
    }
    
    // Validation functions should be [[nodiscard]]
    [[nodiscard]] bool ValidateInput(const std::string& input) const {
        return !input.empty() && input.size() < 100;
    }
    
    [[nodiscard]] bool IsReady() const {
        return initialized_;
    }
    
    // Computational functions should be [[nodiscard]]
    [[nodiscard]] int Calculate(int x, int y) const {
        return x * y + 42;
    }
    
    [[nodiscard]] std::string ProcessData(const Data& data) const {
        return data.ToString();
    }
    
private:
    std::string name_;
    size_t count_;
    bool initialized_;
};
class ResourceFactory {
public:
    // Missing [[nodiscard]] on factory functions
    std::unique_ptr<Resource> CreateResource() {
        return std::make_unique<Resource>();
    }
    
    Resource MakeResource(int id) {
        return Resource{id};
    }
    
    // Missing [[nodiscard]] on getters
    const std::string& GetName() const {
        return name_;
    }
    
    size_t GetCount() const {
        return count_;
    }
    
    // Missing [[nodiscard]] on validation functions
    bool ValidateInput(const std::string& input) const {
        return !input.empty() && input.size() < 100;
    }
    
    bool IsReady() const {
        return initialized_;
    }
    
    // Missing [[nodiscard]] on computational functions
    int Calculate(int x, int y) const {
        return x * y + 42;
    }
    
    std::string ProcessData(const Data& data) const {
        return data.ToString();
    }
    
private:
    std::string name_;
    size_t count_;
    bool initialized_;
};

Smart Pointer by Reference

MODERN_SMART_PTR_BY_REF

Enforces passing smart pointers by value for ownership transfer rather than by reference, which provides clearer ownership semantics.

Examples:

✅ Correct
❌ Incorrect
class ResourceManager {
public:
    // Pass unique_ptr by value for ownership transfer
    void StoreResource(std::unique_ptr<Resource> resource) {
        stored_resource_ = std::move(resource);
    }
    
    // Pass shared_ptr by value for shared ownership
    void AddObserver(std::shared_ptr<Observer> observer) {
        observers_.push_back(observer);
    }
    
    // Move semantics for ownership transfer
    void TransferData(std::unique_ptr<Data>&& data) {
        current_data_ = std::move(data);
    }
    
    // Use raw reference for non-owning access
    void ProcessResource(const Resource& resource) {
        // Function doesn't take ownership
        resource.Process();
    }
    
    // Factory functions return by value
    std::unique_ptr<Resource> CreateResource() {
        return std::make_unique<Resource>();
    }
    
    std::shared_ptr<Cache> GetSharedCache() {
        if (!cache_) {
            cache_ = std::make_shared<Cache>();
        }
        return cache_;
    }
    
private:
    std::unique_ptr<Resource> stored_resource_;
    std::unique_ptr<Data> current_data_;
    std::vector<std::shared_ptr<Observer>> observers_;
    std::shared_ptr<Cache> cache_;
};
class ResourceManager {
public:
    // Don't pass smart pointers by reference
    void StoreResource(const std::unique_ptr<Resource>& resource) {
        // Unclear: does this function take ownership?
        stored_resource_ = resource;  // Won't compile!
    }
    
    // Passing shared_ptr by reference is confusing
    void AddObserver(const std::shared_ptr<Observer>& observer) {
        observers_.push_back(observer);  // Creates a copy anyway
    }
    
    // Reference to unique_ptr is problematic
    void TransferData(std::unique_ptr<Data>& data) {
        current_data_ = std::move(data);  // Modifies caller's pointer
    }
    
    // Inconsistent ownership semantics
    void ProcessResource(std::unique_ptr<Resource>& resource) {
        // Does this function own the resource or just use it?
        resource->Process();
    }
    
    // Returning references to smart pointers
    const std::unique_ptr<Resource>& GetResource() const {
        return stored_resource_;  // Exposes internal implementation
    }
    
    std::shared_ptr<Cache>& GetCache() {
        if (!cache_) {
            cache_ = std::make_shared<Cache>();
        }
        return cache_;  // Allows external modification
    }
    
private:
    std::unique_ptr<Resource> stored_resource_;
    std::unique_ptr<Data> current_data_;
    std::vector<std::shared_ptr<Observer>> observers_;
    std::shared_ptr<Cache> cache_;
};

Summary

Modern C++ rules promote contemporary best practices:

  • Use noexcept: Mark functions that don’t throw exceptions, especially destructors and move operations

  • Const Correctness: Mark methods const when they don’t modify object state

  • [[nodiscard]]: Prevent accidental ignoring of important return values

  • Smart Pointer Semantics: Pass smart pointers by value for clear ownership transfer

These practices lead to more efficient, safer, and more expressive C++ code that takes advantage of modern language features.