Функциональный vs ООП в C++: что выбрать в 2025?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему спор ООП против функционального подхода не утихает?

Если вы когда-нибудь задумывались, какой стиль программирования на C++ выбрать для нового проекта, вы не одиноки. Спор между функциональным программированием и объектно-ориентированным подходом в C++ длится уже не одно десятилетие. С одной стороны — классическое ООП, которое учили в университетах. С другой — современные функциональные техники, которые принёс стандарт C++11, C++14, C++17 и C++20.

На сайте pythonlib.ru мы часто обсуждаем архитектурные решения, и сегодня пришло время разобраться в этом вопросе раз и навсегда. Какой стиль быстрее? Какой проще поддерживать? И главное — можно ли их совмещать?

В этой статье мы не будем фанатично защищать одну парадигму. Вместо этого разберём реальные примеры кода, покажем плюсы и минусы каждого подхода и дадим практические рекомендации. Поехали!

Краткий обзор парадигм в C++

Прежде чем углубляться, давайте быстро освежим в памяти, о чём вообще речь.

Объектно-ориентированное программирование (ООП)

ООП в C++ строится на трёх китах: инкапсуляция, наследование и полиморфизм. Вы создаёте классы, которые объединяют данные и методы их обработки. Это классический подход, который доминировал в 90-х и 2000-х.

class Animal {
public:
    virtual void speak() const = 0;
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() const override {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void speak() const override {
        std::cout << "Meow!" << std::endl;
    }
};

void makeSound(const Animal& animal) {
    animal.speak();
}

Функциональное программирование (ФП)

Функциональный подход в C++ — это не чистый Haskell, а скорее использование идей ФП в рамках императивного языка. Ключевые элементы: лямбда-функции, неизменяемость данных, функции высшего порядка и композиция.

std::vector<int> numbers = {1, 2, 3, 4, 5};

// Функциональный стиль: фильтрация и трансформация
auto result = numbers
    | std::views::filter([](int n) { return n % 2 == 0; })
    | std::views::transform([](int n) { return n * n; });

for (int n : result) {
    std::cout << n << " ";  // Вывод: 4 16
}

Сравнение на практике: три типовые задачи

Давайте возьмём три реальные задачи, с которыми сталкивается разработчик на C++, и посмотрим, как их решают ООП и ФП.

Задача 1: Обработка коллекции данных

Условие: Есть список заказов, нужно отфильтровать дорогие (цена > 1000), отсортировать по дате и вывести топ-5.

ООП-стиль:

class OrderProcessor {
public:
    void addOrder(Order order) { orders_.push_back(std::move(order)); }
    
    std::vector<Order> getTopExpensiveRecent(int count) const {
        auto filtered = orders_;
        filtered.erase(
            std::remove_if(filtered.begin(), filtered.end(),
                           [](const Order& o) { return o.price <= 1000; }),
            filtered.end());
        
        std::sort(filtered.begin(), filtered.end(),
                  [](const Order& a, const Order& b) {
                      return a.date > b.date;
                  });
        
        if (filtered.size() > count) {
            filtered.resize(count);
        }
        return filtered;
    }
    
private:
    std::vector<Order> orders_;
};

Функциональный стиль:

auto getTopExpensiveRecent(const std::vector<Order>& orders, int count) {
    return orders
        | std::views::filter([](const Order& o) { return o.price > 1000; })
        | std::views::sort(std::greater{})
        | std::views::take(count);
}

Вердикт: Функциональный подход короче, читаемее и менее подвержен ошибкам. Нет мутаций состояния, нет временных переменных. Однако ООП даёт больше контроля над промежуточными шагами, если нужна сложная логика.

Задача 2: Обработка ошибок и валидация

Условие: Нужно проверить email пользователя: не пустой, содержит @, длина < 100 символов.

ООП-стиль (цепочка валидаторов):

class Validator {
public:
    virtual bool validate(const std::string& input) const = 0;
    virtual ~Validator() = default;
};

class NotEmptyValidator : public Validator {
    bool validate(const std::string& input) const override {
        return !input.empty();
    }
};

class EmailFormatValidator : public Validator {
    bool validate(const std::string& input) const override {
        return input.find('@') != std::string::npos;
    }
};

class LengthValidator : public Validator {
public:
    explicit LengthValidator(size_t max) : max_(max) {}
    bool validate(const std::string& input) const override {
        return input.size() < max_;
    }
private:
    size_t max_;
};

Функциональный стиль (композиция функций):

bool validateEmail(const std::string& email) {
    auto notEmpty = [](const std::string& s) { return !s.empty(); };
    auto hasAt = [](const std::string& s) { return s.find('@') != std::string::npos; };
    auto maxLength = [](const std::string& s) { return s.size() < 100; };
    
    return notEmpty(email) && hasAt(email) && maxLength(email);
}

Вердикт: Для простых случаев функциональный подход компактнее. Но если валидация сложная и требует конфигурации (например, разные правила для разных типов пользователей), ООП с наследованием даёт больше гибкости.

Задача 3: Параллельная обработка данных

Условие: Есть массив чисел, нужно посчитать сумму квадратов чётных чисел.

ООП-стиль (с ручным управлением потоками):

class ParallelCalculator {
public:
    int compute(const std::vector<int>& data) {
        std::atomic<int> result{0};
        std::vector<std::thread> threads;
        
        auto chunk = data.size() / std::thread::hardware_concurrency();
        
        for (size_t i = 0; i < data.size(); i += chunk) {
            threads.emplace_back([&result, &data, i, chunk]() {
                int local = 0;
                for (size_t j = i; j < std::min(i + chunk, data.size()); ++j) {
                    if (data[j] %                

Блоги

Рекомендаций по книгам