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

Как писать расширение класса 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 контейнеров

Как писать расширение класса std::vector? | PrepBro