Pregunta initializer_list naturaleza inmutable conduce a copia excesiva


¿Por qué el acceso a std::initializer_list no nos permite cambiar su contenido? Es una gran desventaja de std::initializer_list cuando se usa para su propósito principal (para inicializar un contenedor), ya que su uso lleva a una copia / construcción / copia-asignación excesiva en lugar de mover-construir / mover-asignar.

#include <initializer_list>
#include <iostream>
#include <vector>

#include <cstdlib>

struct A
{

    A() = default;
    A(A const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    A(A &&) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    A & operator = (A const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
    A & operator = (A &&) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }

};

int
main()
{
    std::vector< A >{A{}, A{}, A{}};
    return EXIT_SUCCESS;
}

Salida (como se esperaba):

A::A(const A &)
A::A(const A &)
A::A(const A &)

¿Por qué su diseño es tan limitado?


11
2017-11-28 18:51


origen


Respuestas:


Hay un reciente propuesta de listas de inicializadores móviles, donde, en particular, los autores dicen:

std::initializer_list fue diseñado alrededor de 2005 (N1890) a 2007 (N2215), antes   semántica de movimiento madurada, alrededor de 2009. En ese momento, no se anticipó que la semántica de copia   sería insuficiente o incluso subóptimo para las clases comunes similares al valor. Hubo un 2008   propuesta N2801 Lista de inicializadores y semántica de movimiento pero C ++ 0x ya se sentía deslizarse en ese momento, y en 2011 el caso se había enfriado.


6
2017-11-28 19:04



buena (aunque desafortunada) respuesta por Anton.

Aquí está el código fuente de la implementación en libc ++:

template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
vector<_Tp, _Allocator>::vector(initializer_list<value_type> __il)
{
#if _LIBCPP_DEBUG_LEVEL >= 2
    __get_db()->__insert_c(this);
#endif
    if (__il.size() > 0)
    {
        allocate(__il.size());
        __construct_at_end(__il.begin(), __il.end());
    }
}

no hay iteradores de movimiento a la vista, por lo tanto copia la construcción.

en caso de que sea útil, aquí hay una solución utilizando una lista de argumentos variados:

#include <initializer_list>
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <utility>

#include <cstdlib>

struct A
{

    A() noexcept{ std::cout << __PRETTY_FUNCTION__ << std::endl; }
    A(A const &) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    A & operator  = (A const &) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }
    A(A &&) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    A & operator = (A &&) noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this; }

};

template<class T, class...Args>
void append_it(std::vector<T>& v)
{
}

template<class T, class...Args>
void append_it(std::vector<T>& v, T&& t1, Args&&...args)
{
    v.push_back(std::move(t1));
    append_it(v, std::forward<Args&&>(args)...);
}

template<class T, class...Args>
std::vector<T> make_vector(T&& t1, Args&&...args)
{
    std::vector<T> result;
    result.reserve(1 + sizeof...(args));
    result.push_back(std::move(t1));
    append_it(result, std::forward<Args&&>(args)...);
    return result;
}

int
main()
{
    auto v2 = make_vector( A{}, A{}, A{} );

    return EXIT_SUCCESS;
}

4
2017-11-28 19:09