Escribir su propio contenedor STL

120

¿Existen pautas sobre cómo se debe escribir un contenedor nuevo que se comporte como cualquier STLcontenedor?

Avinash
fuente
7
Vea las implementaciones de los contenedores estándar existentes e intente comprenderlas: las funciones, los tipos de retorno, las sobrecargas de operadores, los tipos anidados, la administración de memoria y todo.
Nawaz
Por lo general, comienzo copiando los prototipos de funciones miembro de cualquier contenedor que esté más cerca en concepto de lo que estoy haciendo, ya sea de msdn o del estándar. ( cplusplus.com no tiene funciones de C ++ 11 y www.sgi.com no coincide)
Mooing Duck
@Mooing Duck: ¿crees que msdn está más cerca del estándar que sgi?
Dani
3
Definitivamente lo es. MSDN está actualizado - SGI es pre-estándar
Puppy
9
La mejor referencia en línea (integridad, corrección y especialmente usabilidad) es, con mucho, cppreference.com. También explica un montón de características del lenguaje además de la biblioteca. Y es una wiki, por lo que debería contener menos errores que cplusplus.com.
rubenvb

Respuestas:

209

Esto es una secuencia pseudo-contenedor que armado a partir § 23.2.1 \ 4 Tenga en cuenta que el iterator_categorydebería ser uno de std::input_iterator_tag, std::output_iterator_tag, std::forward_iterator_tag, std::bidirectional_iterator_tag, std::random_access_iterator_tag. También tenga en cuenta que lo siguiente es técnicamente más estricto de lo requerido, pero esta es la idea. Tenga en cuenta que la gran mayoría de las funciones "estándar" son técnicamente opcionales, debido a la genialidad de los iteradores.

template <class T, class A = std::allocator<T> >
class X {
public:
    typedef A allocator_type;
    typedef typename A::value_type value_type; 
    typedef typename A::reference reference;
    typedef typename A::const_reference const_reference;
    typedef typename A::difference_type difference_type;
    typedef typename A::size_type size_type;

    class iterator { 
    public:
        typedef typename A::difference_type difference_type;
        typedef typename A::value_type value_type;
        typedef typename A::reference reference;
        typedef typename A::pointer pointer;
        typedef std::random_access_iterator_tag iterator_category; //or another tag

        iterator();
        iterator(const iterator&);
        ~iterator();

        iterator& operator=(const iterator&);
        bool operator==(const iterator&) const;
        bool operator!=(const iterator&) const;
        bool operator<(const iterator&) const; //optional
        bool operator>(const iterator&) const; //optional
        bool operator<=(const iterator&) const; //optional
        bool operator>=(const iterator&) const; //optional

        iterator& operator++();
        iterator operator++(int); //optional
        iterator& operator--(); //optional
        iterator operator--(int); //optional
        iterator& operator+=(size_type); //optional
        iterator operator+(size_type) const; //optional
        friend iterator operator+(size_type, const iterator&); //optional
        iterator& operator-=(size_type); //optional            
        iterator operator-(size_type) const; //optional
        difference_type operator-(iterator) const; //optional

        reference operator*() const;
        pointer operator->() const;
        reference operator[](size_type) const; //optional
    };
    class const_iterator {
    public:
        typedef typename A::difference_type difference_type;
        typedef typename A::value_type value_type;
        typedef typename const A::reference reference;
        typedef typename const A::pointer pointer;
        typedef std::random_access_iterator_tag iterator_category; //or another tag

        const_iterator ();
        const_iterator (const const_iterator&);
        const_iterator (const iterator&);
        ~const_iterator();

        const_iterator& operator=(const const_iterator&);
        bool operator==(const const_iterator&) const;
        bool operator!=(const const_iterator&) const;
        bool operator<(const const_iterator&) const; //optional
        bool operator>(const const_iterator&) const; //optional
        bool operator<=(const const_iterator&) const; //optional
        bool operator>=(const const_iterator&) const; //optional

        const_iterator& operator++();
        const_iterator operator++(int); //optional
        const_iterator& operator--(); //optional
        const_iterator operator--(int); //optional
        const_iterator& operator+=(size_type); //optional
        const_iterator operator+(size_type) const; //optional
        friend const_iterator operator+(size_type, const const_iterator&); //optional
        const_iterator& operator-=(size_type); //optional            
        const_iterator operator-(size_type) const; //optional
        difference_type operator-(const_iterator) const; //optional

        reference operator*() const;
        pointer operator->() const;
        reference operator[](size_type) const; //optional
    };

    typedef std::reverse_iterator<iterator> reverse_iterator; //optional
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator; //optional

    X();
    X(const X&);
    ~X();

    X& operator=(const X&);
    bool operator==(const X&) const;
    bool operator!=(const X&) const;
    bool operator<(const X&) const; //optional
    bool operator>(const X&) const; //optional
    bool operator<=(const X&) const; //optional
    bool operator>=(const X&) const; //optional

    iterator begin();
    const_iterator begin() const;
    const_iterator cbegin() const;
    iterator end();
    const_iterator end() const;
    const_iterator cend() const;
    reverse_iterator rbegin(); //optional
    const_reverse_iterator rbegin() const; //optional
    const_reverse_iterator crbegin() const; //optional
    reverse_iterator rend(); //optional
    const_reverse_iterator rend() const; //optional
    const_reverse_iterator crend() const; //optional

    reference front(); //optional
    const_reference front() const; //optional
    reference back(); //optional
    const_reference back() const; //optional
    template<class ...Args>
    void emplace_front(Args&&...); //optional
    template<class ...Args>
    void emplace_back(Args&&...); //optional
    void push_front(const T&); //optional
    void push_front(T&&); //optional
    void push_back(const T&); //optional
    void push_back(T&&); //optional
    void pop_front(); //optional
    void pop_back(); //optional
    reference operator[](size_type); //optional
    const_reference operator[](size_type) const; //optional
    reference at(size_type); //optional
    const_reference at(size_type) const; //optional

    template<class ...Args>
    iterator emplace(const_iterator, Args&&...); //optional
    iterator insert(const_iterator, const T&); //optional
    iterator insert(const_iterator, T&&); //optional
    iterator insert(const_iterator, size_type, T&); //optional
    template<class iter>
    iterator insert(const_iterator, iter, iter); //optional
    iterator insert(const_iterator, std::initializer_list<T>); //optional
    iterator erase(const_iterator); //optional
    iterator erase(const_iterator, const_iterator); //optional
    void clear(); //optional
    template<class iter>
    void assign(iter, iter); //optional
    void assign(std::initializer_list<T>); //optional
    void assign(size_type, const T&); //optional

    void swap(X&);
    size_type size() const;
    size_type max_size() const;
    bool empty() const;

    A get_allocator() const; //optional
};
template <class T, class A = std::allocator<T> >
void swap(X<T,A>&, X<T,A>&); //optional

Además, cada vez que hago un contenedor, pruebo con una clase más o menos como esta:

#include <cassert>
struct verify;
class tester {
    friend verify;
    static int livecount;
    const tester* self;
public:
    tester() :self(this) {++livecount;}
    tester(const tester&) :self(this) {++livecount;}
    ~tester() {assert(self==this);--livecount;}
    tester& operator=(const tester& b) {
        assert(self==this && b.self == &b);
        return *this;
    }
    void cfunction() const {assert(self==this);}
    void mfunction() {assert(self==this);}
};
int tester::livecount=0;
struct verify {
    ~verify() {assert(tester::livecount==0);}
}verifier;

Haga contenedores de testerobjetos y llame a cada uno function()mientras prueba su contenedor. No hagas ningún testerobjeto global . Si su contenedor hace trampa en algún lugar, esta testerclase lo hará asserty usted sabrá que hizo trampa accidentalmente en algún lugar.

Pato morando
fuente
1
Esto es interesante. ¿Cómo funciona tu probador? Hay varios errores de análisis, que son triviales (faltan ';') pero no estoy seguro de cómo funciona ese destructor de verificación. Oh, querías decir assert(tester::livecount == 0);. Mmmmm, todavía no estoy seguro de cómo funciona este marco de prueba. ¿Podría darnos un ejemplo?
Adrian
2
El probador tiene un solo miembro no estático que es un puntero a sí mismo, y el destructor y los miembros son una forma de verificar que no es válido memcpy ocurrido . (la prueba no es infalible, pero detecta algunos). El livecountes un detector de fugas simple, para asegurarse de que su contenedor llame a un número igual de constructores y destructores.
Mooing Duck
Ok, lo veo, pero ¿cómo prueba tu iterador? Por cierto, creo que quisiste decir que verifierno varifier.
Adrian
4
@Adrian No, no, escribe tu contenedor, y luego pones un montón de estos en el contenedor, y haces cosas con el contenedor, para verificar que no accidentalmente memcpy, y recuerdas llamar a todos los destructores.
Mooing Duck
1
¿Puedo sugerir heredar el iterador del std::iteratorencabezado<iterator>
sp2danny
28

Deberá leer la sección del estándar C ++ sobre contenedores y los requisitos que impone el estándar C ++ para las implementaciones de contenedores.

El capítulo relevante en el estándar C ++ 03 es:

Sección 23.1 Requisitos del contenedor

El capítulo relevante en el estándar C ++ 11 es:

Sección 23.2 Requisitos del contenedor

El borrador casi final del estándar C ++ 11 está disponible gratuitamente aquí .

También puede leer algunos libros excelentes que lo ayudarán a comprender los requisitos desde la perspectiva del usuario del contenedor. Dos libros excelentes que me llamaron la atención con facilidad son:

STL eficaz deScott Meyers y
la biblioteca estándar de C ++: un tutorial y una referencia deNicolai Josutils

Alok Save
fuente
6

Aquí hay una implementación muy simplista de un vector falso, que es básicamente un envoltorio std::vectory tiene su propio (pero real) iterador, que imita el iterador STL. Nuevamente, el iterador es muy simplista, omitiendo muchos conceptos como const_iterator, verificaciones de validez, etc.

El código se puede ejecutar desde el primer momento.

#include <iostream>
#include <string>
#include <vector>

template<typename T>
struct It
{
    std::vector<T>& vec_;
    int pointer_;

    It(std::vector<T>& vec) : vec_{vec}, pointer_{0} {}

    It(std::vector<T>& vec, int size) : vec_{vec}, pointer_{size} {}

    bool operator!=(const It<T>& other) const
    {
        return !(*this == other);
    }

    bool operator==(const It<T>& other) const
    {
        return pointer_ == other.pointer_;
    }

    It& operator++()
    {
        ++pointer_;            
        return *this;
    }

    T& operator*() const
    {
        return vec_.at(pointer_);   
    }
};

template<typename T>
struct Vector
{
    std::vector<T> vec_;

    void push_back(T item)
    {
        vec_.push_back(item);
    };

    It<T> begin()
    {
        return It<T>(vec_);
    }

    It<T> end()
    {
        return It<T>(vec_, vec_.size());
    }
};

int main()
{
  Vector<int> vec;
  vec.push_back(1);
  vec.push_back(2);
  vec.push_back(3);

  bool first = true;
  for (It<int> it = vec.begin(); it != vec.end(); ++it)
  {
      if (first) //modify container once while iterating
      {
          vec.push_back(4);
          first = false;
      }

      std::cout << *it << '\n'; //print it 
      (*it)++;                  //change it
  }

  for (It<int> it = vec.begin(); it != vec.end(); ++it)
  {
      std::cout << *it << '\n'; //should see changed value
  }
}
PoweredByRice
fuente