Как ограничить количество объектов класса в C++

Ответ

В C++ для ограничения числа экземпляров класса обычно используют статический счетчик. Вот надежная реализация:

#include <stdexcept>

class SingletonLike {
    static inline int instanceCount = 0; // Счетчик существующих объектов
    static constexpr int MAX_INSTANCES = 3; // Жесткий лимит

public:
    SingletonLike() {
        // Проверяем лимит в конструкторе
        if (instanceCount >= MAX_INSTANCES) {
            throw std::runtime_error("Cannot create more than " + 
                                     std::to_string(MAX_INSTANCES) + " instances");
        }
        ++instanceCount;
        std::cout << "Instance created. Total: " << instanceCount << std::endl;
    }

    // Важно запретить копирование и присваивание, чтобы обойти лимит
    SingletonLike(const SingletonLike&) = delete;
    SingletonLike& operator=(const SingletonLike&) = delete;

    // Разрешаем перемещение (перехватываем владение)
    SingletonLike(SingletonLike&& other) noexcept {
        // При перемещении счетчик не увеличивается
    }

    ~SingletonLike() {
        --instanceCount;
        std::cout << "Instance destroyed. Total: " << instanceCount << std::endl;
    }

    static int getLiveInstanceCount() { return instanceCount; }
};

// Использование
int main() {
    try {
        SingletonLike a;
        SingletonLike b;
        SingletonLike c;
        SingletonLike d; // Выбросит исключение
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

Альтернативный подход — Singleton (один экземпляр):

class Singleton {
    Singleton() = default; // Приватный конструктор
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        static Singleton instance; // Создается один раз при первом вызове
        return instance;
    }
};

Важные моменты:

  1. Счетчик должен быть статическим — он принадлежит классу, а не объекту.
  2. Проверка и инкремент — в конструкторе, декремент — в деструкторе.
  3. Необходимо запретить копирование, иначе можно обойти лимит через SingletonLike b = a;.
  4. Использование inline для статической переменной (C++17) избавляет от необходимости отдельного определения в .cpp файле.

Ответ 18+ 🔞

Э, слушай, вот это тема! Ну, то есть, ты хочешь, чтобы твой класс как какой-нибудь дефицитный товар был — больше трёх штук не выдавать, а то, блядь, на всех не хватит. Чистая бюрократия в коде, ёпта.

Так, смотри сюда. Главная фишка — статический счётчик. Это, типа, глобальная табличка у класса на двери, где он палочками считает, сколько его клонов уже бегает по программе. Если палочек больше трёх — всё, приехали, новых не пускаем.

Вот смотри на этот код, я его чуть поясню, а то ты, может, не въедешь.

#include <stdexcept>

class SingletonLike {
    static inline int instanceCount = 0; // Вот эта палочка, считает живых
    static constexpr int MAX_INSTANCES = 3; // А это лимит, его не превысить

public:
    SingletonLike() {
        // Заходишь в конструктор — первым делом на счётчик глянь
        if (instanceCount >= MAX_INSTANCES) {
            throw std::runtime_error("Cannot create more than " + 
                                     std::to_string(MAX_INSTANCES) + " instances");
        }
        ++instanceCount; // Всё чисто — добавляй ещё одну палочку
        std::cout << "Instance created. Total: " << instanceCount << std::endl;
    }

    // А вот это, блядь, ВАЖНЕЙШЕЕ правило! УДАЛЯЕМ копирование!
    SingletonLike(const SingletonLike&) = delete;
    SingletonLike& operator=(const SingletonLike&) = delete;

    // Перемещение разрешаем, там счётчик не трогаем, просто право владения переходит
    SingletonLike(SingletonLike&& other) noexcept {
        // Пусто, потому что при перемещении новый объект не создаётся, старый умирает
    }

    ~SingletonLike() {
        --instanceCount; // Объект помер — вычёркивай палочку
        std::cout << "Instance destroyed. Total: " << instanceCount << std::endl;
    }

    static int getLiveInstanceCount() { return instanceCount; }
};

Представляешь, что будет, если копирование оставить? Овердохуища объектов наделаешь! Написал SingletonLike b = a; — и всё, лимит ебёныш, система послана нахуй. Поэтому delete, и точка.

А теперь, чувак, главный подвох, про который все забывают! Смотри:

int main() {
    try {
        SingletonLike a; // 1
        SingletonLike b; // 2
        SingletonLike c; // 3
        SingletonLike d; // А вот тут будет тебе "хиросима и нигерсраки"! Исключение вылетит!
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl; // И получишь ты в лог: "Cannot create more than 3 instances"
    }
}

Всё, четвёртому пизда рулю. Конструктор ругнётся и выкинет тебя с позором.


Ну а если тебе нужен совсем уж царь-объект, один-единственный?

Тогда это классический Singleton, ёперный театр. Тут уж не три, а один, и всё.

class Singleton {
    Singleton() = default; // Конструктор спрятан, с улицы не вызвать
public:
    // Опять же, копирование и присваивание — под запретом
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // А вот этот метод — единственные ворота
    static Singleton& getInstance() {
        static Singleton instance; // Магия! Создастся один раз при первом вызове
        return instance; // И всегда будешь получать ссылку на него
    }
};

Вот это, блядь, красота. Хоть сто раз вызывай getInstance() — тебе всегда один и тот же объект подсунут. Доверия ебать ноль, но зато надёжно.

Коротко по делу, чтобы в голове осело:

  1. Статический счётчик — это как швейцар у класса, считает, кто вошёл и вышел.
  2. Лимит проверяй в конструкторе — не пускай лишних.
  3. Удали копирование (= delete) — это святое! Иначе какой-нибудь распиздяй обойдёт твою защиту.
  4. inline переменная (C++17) — удобно, не надо её в .cpp файле отдельно определять.
  5. Деструктор не забудь — когда объект умирает, швейцар должен вычеркнуть его из списка.

Вот и вся магия. Сделаешь так — будет тебе счастье, а не код. Главное, не проёбывай пункт с удалением копирования, а то будет тебе не ограничение, а манда с ушами.