← Назад к вопросам

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

2.0 Middle🔥 122 комментариев
#ООП и проектирование

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ограничение количества объектов класса

В C++ есть несколько способов ограничить количество экземпляров класса. Это полезно для контроля ресурсов, реализации Singleton или других паттернов.

Способ 1: Счетчик экземпляров

Применяем статические переменные для отслеживания количества объектов:

class LimitedClass {
private:
    static constexpr int MAX_INSTANCES = 3;
    static int instance_count;
    
public:
    LimitedClass() {
        if (instance_count >= MAX_INSTANCES) {
            throw std::runtime_error("Maximum instances reached");
        }
        instance_count++;
        std::cout << "Instance " << instance_count << " created\n";
    }
    
    ~LimitedClass() {
        instance_count--;
        std::cout << "Instance deleted, count: " << instance_count << "\n";
    }
    
    static int getInstanceCount() {
        return instance_count;
    }
};

int LimitedClass::instance_count = 0;

int main() {
    LimitedClass obj1;  // OK
    LimitedClass obj2;  // OK
    LimitedClass obj3;  // OK
    LimitedClass obj4;  // Ошибка: std::runtime_error
}

Способ 2: Приватный конструктор + static factory method (Singleton)

Для ограничения в один объект:

class Singleton {
private:
    static Singleton* instance;
    
    // Приватный конструктор
    Singleton() {
        std::cout << "Singleton created\n";
    }
    
public:
    // Удаляем копирование и перемещение
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
    
    ~Singleton() {
        std::cout << "Singleton destroyed\n";
    }
    
    // Static method для получения единственного экземпляра
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
    void doSomething() {
        std::cout << "Doing something\n";
    }
};

Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    
    assert(s1 == s2);  // Один и тот же объект
    s1->doSomething();
}

Способ 3: Meyers Singleton (потокобезопасный)

Современный способ с гарантией потокобезопасности:

class ThreadSafeSingleton {
private:
    // Приватный конструктор
    ThreadSafeSingleton() {
        std::cout << "ThreadSafeSingleton created\n";
    }
    
public:
    // Удаляем копирование
    ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
    ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
    
    ~ThreadSafeSingleton() {
        std::cout << "ThreadSafeSingleton destroyed\n";
    }
    
    // Static method с локальной статической переменной
    // Гарантирует создание один раз и потокобезопасно (C++11)
    static ThreadSafeSingleton& getInstance() {
        static ThreadSafeSingleton instance;
        return instance;
    }
};

int main() {
    ThreadSafeSingleton& s1 = ThreadSafeSingleton::getInstance();
    ThreadSafeSingleton& s2 = ThreadSafeSingleton::getInstance();
    
    assert(&s1 == &s2);  // Один и тот же объект
}

Способ 4: Объектный пул (Object Pool)

Для переиспользования объектов с ограничением:

class PooledResource {
private:
    int id;
    bool in_use;
    
public:
    PooledResource(int i) : id(i), in_use(false) {}
    
    int getId() const { return id; }
    bool isInUse() const { return in_use; }
    void setInUse(bool use) { in_use = use; }
    
    void reset() {
        in_use = false;
    }
};

class ObjectPool {
private:
    static constexpr int MAX_RESOURCES = 5;
    std::vector<std::unique_ptr<PooledResource>> pool;
    std::mutex mutex;
    
public:
    ObjectPool() {
        for (int i = 0; i < MAX_RESOURCES; ++i) {
            pool.push_back(std::make_unique<PooledResource>(i));
        }
    }
    
    std::shared_ptr<PooledResource> acquire() {
        std::lock_guard<std::mutex> lock(mutex);
        
        for (auto& resource : pool) {
            if (!resource->isInUse()) {
                resource->setInUse(true);
                return std::shared_ptr<PooledResource>(
                    resource.get(),
                    [this](PooledResource* r) {
                        std::lock_guard<std::mutex> lk(mutex);
                        r->reset();
                    }
                );
            }
        }
        
        throw std::runtime_error("No resources available");
    }
    
    int getAvailableCount() {
        std::lock_guard<std::mutex> lock(mutex);
        return std::count_if(pool.begin(), pool.end(),
            [](const auto& r) { return !r->isInUse(); });
    }
};

int main() {
    ObjectPool pool;
    
    {
        auto res1 = pool.acquire();
        auto res2 = pool.acquire();
        
        std::cout << "Available: " << pool.getAvailableCount() << "\n";  // 3
    }  // res1, res2 освобождены
    
    std::cout << "Available: " << pool.getAvailableCount() << "\n";  // 5
}

Способ 5: CRTP с ограничением

Для более элегантного решения:

template<typename T, int MAX_INSTANCES>
class LimitedInstancesBase {
private:
    static int instance_count;
    static std::mutex count_mutex;
    
protected:
    LimitedInstancesBase() {
        std::lock_guard<std::mutex> lock(count_mutex);
        if (instance_count >= MAX_INSTANCES) {
            throw std::runtime_error("Instance limit exceeded");
        }
        instance_count++;
    }
    
    ~LimitedInstancesBase() {
        std::lock_guard<std::mutex> lock(count_mutex);
        instance_count--;
    }
    
public:
    static int getInstanceCount() {
        std::lock_guard<std::mutex> lock(count_mutex);
        return instance_count;
    }
};

template<typename T, int MAX_INSTANCES>
int LimitedInstancesBase<T, MAX_INSTANCES>::instance_count = 0;

template<typename T, int MAX_INSTANCES>
std::mutex LimitedInstancesBase<T, MAX_INSTANCES>::count_mutex;

// Использование:
class MyService : public LimitedInstancesBase<MyService, 2> {
public:
    MyService() {
        std::cout << "MyService instance " << getInstanceCount() << " created\n";
    }
};

int main() {
    MyService s1;  // OK, count = 1
    MyService s2;  // OK, count = 2
    MyService s3;  // Ошибка!
}

Потокобезопасность

Для многопоточных приложений:

class ThreadSafePool {
private:
    static constexpr int MAX_INSTANCES = 10;
    static std::atomic<int> instance_count;
    
public:
    ThreadSafePool() {
        int current = instance_count++;
        if (current >= MAX_INSTANCES) {
            instance_count--;
            throw std::runtime_error("Limit exceeded");
        }
    }
    
    ~ThreadSafePool() {
        instance_count--;
    }
    
    static int getCount() {
        return instance_count.load();
    }
};

std::atomic<int> ThreadSafePool::instance_count(0);

Сравнение подходов

МетодПростотаПотокобезопасностьПереиспользование
СчетчикПростойТребует синхронизацииНет
Singleton (static)ПростойВстроеннаяНет
Meyers SingletonЭлегантныйВстроеннаяНет
Object PoolСложныйДаДа
CRTPУниверсальныйОпциональноНет

Практический пример: Database connections

class ConnectionPool {
private:
    static constexpr int MAX_CONNECTIONS = 50;
    std::queue<std::unique_ptr<Connection>> available;
    std::mutex mutex;
    int total_connections = 0;
    
public:
    std::shared_ptr<Connection> getConnection() {
        std::lock_guard<std::mutex> lock(mutex);
        
        if (available.empty()) {
            if (total_connections < MAX_CONNECTIONS) {
                total_connections++;
                return std::make_shared<Connection>();
            }
            throw std::runtime_error("No connections available");
        }
        
        auto conn = std::move(available.front());
        available.pop();
        return std::shared_ptr<Connection>(
            conn.release(),
            [this](Connection* c) {
                std::lock_guard<std::mutex> lk(mutex);
                available.push(std::unique_ptr<Connection>(c));
            }
        );
    }
};

Вывод

  • Для Singleton → используй Meyers Singleton
  • Для ограничения объектов → счетчик + CRTP
  • Для переиспользования ресурсов → Object Pool
  • Для потокобезопасности → std::atomic или std::mutex
Как ограничить количество объектов класса? | PrepBro