Semánticas de copia y movimiento (VI)

Artículos de la serie:

Últimos pasos. Sentencias de control for basadas en rango


En este último post de la serie, mostraremos el código completo de la plantilla Dynarray<> (que debería incluirse en un espacio de nombres conveniente), introduciendo funciones miembro que permitan iterar a través del array, acceder a sus elementos (con y sin verificación de rango), etcétera.La mayor parte del código es autoexplicativo y descansa en las discusiones realizadas en artículos anteriores:

   namespace detail {       // incluir aquí la definición de Dynarray_base<>    }    template<typename T>    class Dynarray : protected detail::Dynarray_base<T> {       using Base = detail::Dynarray_base<T>;       using Base::first;       using Base::count;    public:       // typedefs:       using value_type             = T;       using const_reference        = T const&;       using reference              = T&;       using const_iterator         = T const*;       using iterator               = T*;       using const_reverse_iterator = std::reverse_iterator<const_iterator>;       using reverse_iterator       = std::reverse_iterator<iterator>;       using size_type              = std::size_t;       // construct/copy/move/destroy:       explicit Dynarray(size_type n = 0)          : Base{n}       {          auto current = first;          try {             for (; n > 0; ++current, --n)                ::new(static_cast<void*>(current)) value_type;          }          catch (...) {             for (auto p = first; p != current; ++p)                p->~value_type();             throw;          }       }       Dynarray(size_type n, value_type const& val)          : Base{n}       {          std::uninitialized_fill_n(first,n,val);       }       Dynarray(std::initializer_list<value_type> const& init)          : Base{init.size()}       {          std::uninitialized_copy(init.begin(),init.end(),first);       }       Dynarray(Dynarray const& d)          : Base{d.size()}       {          std::uninitialized_copy(d.begin(),d.end(),first);       }       auto& operator=(Dynarray const& d)       {          Dynarray<value_type> tmp{d};          swap(tmp);          return *this;       }       Dynarray(Dynarray&& d) noexcept          : Base{0}       {          swap(d);       }       auto& operator=(Dynarray&& d) noexcept       {          Dynarray<value_type> tmp;          swap(tmp);          swap(d);          return *this;       }       ~Dynarray()       {          for (auto p = begin(); p != end(); ++p)             p->~value_type();       }       // iterators:       auto begin() const noexcept { return const_iterator{first}; }       auto begin() noexcept { return first; }       auto end() const noexcept { return const_iterator{first + count}; }       auto end() noexcept { return first + count; }       auto cbegin() const noexcept { return const_iterator{first}; }       auto cend() const noexcept { return const_iterator{first + count}; }       auto rbegin() const noexcept { return const_reverse_iterator{cend()}; }       auto rbegin() noexcept { return reverse_iterator{end()}; }       auto rend() const noexcept { return const_reverse_iterator{first}; }       auto rend() noexcept { return reverse_iterator{first}; }       auto crbegin() const noexcept { return const_reverse_iterator{cend()}; }       auto crend() const noexcept { return const_reverse_iterator{first}; }       // capacity:       auto size()  const noexcept -> size_type { return count; }       [[nodiscard]] auto empty() const noexcept -> bool { return count == 0; }       // element access:       auto operator[](size_type i) const -> const_reference { return first[i]; }       auto operator[](size_type i) -> reference { return first[i]; }       auto at(size_type i) const -> const_reference { rangeCheck(i); return first[i]; }       auto at(size_type i) -> reference { rangeCheck(i); return first[i]; }       // data access:       auto data() const noexcept -> T const* { return first; }       auto data() noexcept -> T* { return first; }       // modifiers:       void swap(Dynarray& d) noexcept       {          std::swap(first,d.first);          std::swap(count,d.count);       }    private:       void rangeCheck(size_type i) const       {          if (i >= size)             throw std::out_of_range{"Out of range index"};       }    };


En particular, las funciones miembro públicas begin() y end() permiten iterar a través de la matriz unidimensional en el sentido habitual, desde el primer elemento al último (es decir, según la secuencia de índices de acceso 0,1,2,...,n-1, siendo n el número total de entradas en el array). La función begin() retorna un iterador que apunta al primer elemento de la matriz, mientras que end() lo hace a un hipotético elemento de tipo value_type posterior al último (véase la figura superior). Las versiones constantes de dichas funciones, invocables por arrays constantes, retornan punteros a elementos constantes, de modo que el contenedor no pueda modificarse. Así operan también, por definición, cbegin() y cend(), si bien estas funciones pueden ser invocadas por objetos no constantes. Las funciones rbegin() y rend() (así como crbegin() y crend()) permiten iterar la matriz en orden inverso (desde el último elemento al primero, según la secuencia de acceso n-1,n-2,...,2,1,0). El siguiente bloque de código proporciona varios ejemplos de uso:

   auto d = Dynarray<int>{0,1,2,3,4};    for (auto p = d.cbegin(); p != d.cend(); ++p)       std::cout << *p << ' ';                      // output: 0 1 2 3 4    for (auto p = d.begin(); p != d.end(); ++p)       *p *= 2;                                     // multiplicamos cada entrada por 2    for (auto p = d.crbegin(); p != d.crend(); ++p)       std::cout << *p << ' ';                      // output: 8 6 4 2 0

Conviene resaltar aquí la sintaxis simplificada que los estándares del lenguaje C++ y posteriores ofrecen para recorrer por iteración cualquier rango definido por un par de funciones begin() y end(). Nos referimos a la denominada sentencia de control for basada en rango [1]. Así, los dos primeros bucles del código anterior podrían reescribirse como:

   auto d = Dynarray<int>{0,1,2,3,4};    for (auto x : d)       std::cout << x << ' '; // output: 0 1 2 3 4    for (auto& x : d)       x *= 2;                // multiplicamos cada entrada por 2

La sentencia for (auto x : d) en el primer bucle de ejemplo puede leerse como "para cada elemento x en el contenedor d". En este caso, se realiza una copia de cada elemento (se habla entonces de acceso por valor) para enviarla a la salida estándar. Si la copia de las entradas de la matriz fuese una operación costosa y, como en el caso considerado, no fuese necesario modificar sus valores, convendría tomar referencias a elementos constantes, escribiéndose en su lugar for (auto const& x : d) (acceso por referencia a objeto constante). En nuestro caso no es necesario proceder así, dado que trabajamos con enteros int: ¡una referencia puede ser más costosa que una copia! En el segundo bucle de ejemplo, se toma una referencia no-constante de cada elemento de la matriz con el fin de poder modificar su valor, optándose por la forma for (auto& x : d) (acceso por referencia).


Referencias bibliográficas:
  1. Range-for loop - https://en.cppreference.com/w/cpp/language/range-for
  2. C++ Dynamic Arrays proposal (N3662) - https://isocpp.org/files/papers/N3662.html

No hay comentarios:

Publicar un comentario