initializer_list неизменна природа води до прекомерно копиране

Защо достъпът до std::initializer_list не ни позволява да променяме съдържанието му? Това е голям недостатък на std::initializer_list, когато се използва за основната му цел (да инициализира контейнер), тъй като използването му води до прекомерно копиране-конструкция/копиране-присвояване, вместо преместване-конструкция/преместване-присвояване.

#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;
}

Резултат (както се очаква):

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

Защо неговият дизайн е толкова ограничен?


person Tomilov Anatoliy    schedule 28.11.2014    source източник
comment
Както мога да заключа, {A{}, A{}, A{}} конструира цялото съдържание на списъка с инициализатор на място.   -  person Tomilov Anatoliy    schedule 28.11.2014
comment
Също така е невъзможно да се използва std::initializer_list за инициализиране на контейнери на обекти, които не могат да се копират.   -  person Tomilov Anatoliy    schedule 30.11.2014


Отговори (2)


Има скорошно предложение за списъци с подвижни инициализатори, където по-специално авторите казват:

std::initializer_list е проектиран около 2005 г. (N1890) до 2007 г. (N2215), преди семантиката на преместване да узрее, около 2009 г. По това време не се очакваше семантиката на копиране да бъде недостатъчна или дори неоптимална за общи класове, подобни на стойност. Имаше предложение от 2008 г. N2801 Списъци на инициализатори и семантика на преместване, но по това време вече се усещаше, че C++0x се подхлъзва и до 2011 г. случаят беше охладен.

person Anton Savin    schedule 28.11.2014

добър (ако е жалък) отговор от Антон.

Ето изходния код на внедряването в 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());
    }
}

не се виждат итератори на движение, следователно конструкция на копиране.

в случай, че е полезно, ето заобиколно решение с помощта на списък с променливи аргументи:

#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;
}
person Richard Hodges    schedule 28.11.2014