← Назад к вопросам
Как ограничить количество объектов класса?
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