← Назад к вопросам
Как писать расширение класса std::vector?
1.7 Middle🔥 91 комментариев
#STL контейнеры и алгоритмы#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как писать расширение класса std::vector?
Важное предупреждение
std::vector НЕЛЬЗЯ наследовать (у него non-virtual деструктор). Это приведёт к undefined behavior:
// ❌ ОПАСНО
class MyVector : public std::vector<int> { // БАД ИДЕЯ
public:
void customMethod() { }
};
std::vector<int>* v = new MyVector();
delete v; // Вызовет std::vector деструктор, не MyVector
// Утечка памяти!
Способ 1: Композиция (рекомендуется)
Вместо наследования используем композицию:
template <typename T>
class MyVector {
private:
std::vector<T> data;
public:
// Wrapper методы
void push_back(const T& value) {
data.push_back(value);
onDataChanged();
}
T& operator[](size_t i) {
return data[i];
}
size_t size() const {
return data.size();
}
// Собственные методы
void customMethod() { }
private:
void onDataChanged() {
// логика при изменении
}
};
Способ 2: Adapter паттерн
Если нужно расширить интерфейс:
template <typename T>
class VectorAdapter {
private:
std::vector<T> data;
public:
// Основные операции
auto begin() { return data.begin(); }
auto end() { return data.end(); }
size_t size() const { return data.size(); }
// Новые методы
T getFirst() const {
if (data.empty()) throw std::runtime_error("Empty");
return data.front();
}
T getLast() const {
if (data.empty()) throw std::runtime_error("Empty");
return data.back();
}
void reverse() {
std::reverse(data.begin(), data.end());
}
};
Способ 3: Template specialization
Для специализации типа:
template <typename T>
class Vector : public std::vector<T> { // ❌ Всё ещё опасно!
// ...
};
// Но если АБСОЛЮТНО нужна полная функциональность,
// можно использовать virtual деструктор:
class VectorBase {
public:
virtual ~VectorBase() = default;
virtual void add(int x) = 0;
};
template <typename T>
class Vector : public VectorBase {
private:
std::vector<T> data;
public:
void add(int x) override {
data.push_back(x);
}
};
Способ 4: CRTP для compile-time полиморфизма
Если нужна специализация БЕЗ runtime overhead:
template <typename Derived>
class VectorBase {
public:
void push_back(int value) {
static_cast<Derived*>(this)->push_backImpl(value);
}
protected:
std::vector<int> data;
};
class MyVector : public VectorBase<MyVector> {
public:
void push_backImpl(int value) {
data.push_back(value);
onDataChanged();
}
private:
void onDataChanged() { /* ... */ }
};
Практический пример: Vector с историей
template <typename T>
class HistoryVector {
private:
std::vector<T> data;
std::vector<std::vector<T>> history;
public:
void push_back(const T& value) {
data.push_back(value);
// Сохраняем снимок состояния
history.push_back(data);
}
void undo() {
if (history.empty()) return;
data = history.back();
history.pop_back();
}
size_t getHistorySize() const {
return history.size();
}
// Делегируем методы вектора
T& operator[](size_t i) { return data[i]; }
size_t size() const { return data.size(); }
bool empty() const { return data.empty(); }
};
// Использование
int main() {
HistoryVector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
std::cout << vec.size() << " elements, "
<< vec.getHistorySize() << " changes\n";
vec.undo();
std::cout << vec.size() << " elements after undo\n";
}
Способ 5: Статическое расширение (template)
Добавляем методы БЕЗ изменения класса:
// Общий template для всех типов vector
template <typename T>
T getMiddle(const std::vector<T>& v) {
if (v.empty()) throw std::runtime_error("Empty");
return v[v.size() / 2];
}
// Использование
std::vector<int> vec = {1, 2, 3, 4, 5};
int middle = getMiddle(vec); // 3
Что НЕ делать
// ❌ Наследование от std::vector
class BadVector : public std::vector<int> { };
// ❌ Изменение std namespace
namespace std {
template <>
class vector<MyType> { }; // Undefined behavior!
}
// ❌ Публичное наследование без virtual деструктора
class Danger : public std::vector<int> {
public:
~Danger() { std::cout << "Danger destructor\n"; }
};
Когда использовать что
| Случай | Решение |
|---|---|
| Добавить методы | Композиция или adapter |
| Специализировать для типа | Template specialization |
| Логирование всех операций | Composition с wrapper |
| Изменять поведение | Adapter + virtual (если нужно) |
| Compile-time оптимизация | CRTP |
Заключение
- НЕ наследуйте от std::vector — non-virtual деструктор
- Используйте композицию — это правильный способ
- Adapter паттерн для добавления функциональности
- CRTP для compile-time специализации
- Template специализация для разных типов
Композиция > Наследование от STL контейнеров