std :: vector init с фигурными скобками дважды вызывает конструктор копирования

Почему, когда я инициализирую std :: vector с фигурными скобками

std::vector<TS> vec {ts1, ts2};

Компилятор дважды вызывает оператор конструктора копирования? С другой стороны - с push_back он вызывается только один раз.

#include <iostream>
#include <vector>
using namespace std;

struct TS{
    TS(){
        cout<<"default constructor\n";
    }

    TS(const TS &other) {
        cout<<"Copy constructor\n";
    }

    TS(TS &&other) noexcept{
        cout<<"Move constructor\n";
    }

    TS& operator=(TS const& other)
    {
        cout<<"Copy assigment\n";
        return *this;
    }

    TS& operator=(TS const&& other) noexcept
    {
        cout<<"Move assigment\n";
        return *this;
    }

    ~TS(){
        cout<<"destructor\n";
    }

};

int main() {
    TS ts1;
    TS ts2;
    cout<<"-----------------------------------------\n";
    std::vector<TS> vec {ts1, ts2};
    //vec.push_back(ts1);
    //vec = {ts1, ts2};
    cout<<"-----------------------------------------\n";



    return 0;
}

http://ideone.com/qcPG7X


person tower120    schedule 10.12.2013    source источник
comment
Оператор присваивания не должен вызываться с помощью brace-init, но аргументы копируются в std::initializer_list<TS> и копируются = ›два вызова конструктора копирования для каждого аргумента. Вы можете переместить их в initializer_list (std::vector<TS> vec {std::move(ts1), std::move(ts2)};), но вы не можете переместить их, поэтому необходим хотя бы один вызов конструктора копирования.   -  person dyp    schedule 10.12.2013
comment
Могу я хоть как-то заставить его использовать задание перемещения вместо того, чтобы справиться?   -  person tower120    schedule 10.12.2013
comment
Не с инициализацией списка. Однако вы можете написать обходные пути, например функцию, которая возвращает тип, который хранит аргументы функции как ссылки и который обеспечивает преобразование в std::vector, называемое à la vector<TS> vec( collect_references(ts1, ts2) );.   -  person dyp    schedule 10.12.2013
comment
Функция @DyP должна возвращать std :: vector. Хорошо, хорошая идея, спасибо.   -  person tower120    schedule 10.12.2013
comment
Нет, потому что сейчас не то, что value_type. Он должен возвращать тип с template<class T> operator std::vector<T>() const, чтобы вывести value_type для инициализируемого вектора; затем он может использовать emplace_back и по возможности избегать любого copy-ctor. Живой пример   -  person dyp    schedule 10.12.2013
comment
К сожалению, я забыл std::forward соответственно;) Исправлен живой пример   -  person dyp    schedule 11.12.2013
comment
@DyP Что это означает auto collect_references (Ts && ... pp) - ›reference_collector‹ Ts && ... ›просто вывод типа? И вы делаете что-то новое с этим std :: forward ‹Ts› (pp)? Честно говоря, немного усложняю для меня.   -  person tower120    schedule 11.12.2013
comment
auto function_name(parameter_list) -> return_type - это просто еще один способ записи return_type function_name(parameter_list) (где вы можете использовать имена параметров в возвращаемом типе); Ts&&... здесь объявляет пакет параметров функции, где каждый параметр является универсальной ссылкой. Я согласен с тем, что это сложно, но это должно быть невидимо для пользователя collect_references. Каждый переданный вами аргумент просто перенаправляется на vec.emplace_back вызов без какого-либо преобразования, копирования или перемещения.   -  person dyp    schedule 11.12.2013
comment
Но чтобы избежать копирования, мы должны использовать std :: move для каждого аргумента collect_references?   -  person tower120    schedule 11.12.2013
comment
Как видно из моего фиксированного примера, вы можете использовать std::move для хранения ссылки rvalue в сборщике ссылок; тогда аргумент будет перемещен ровно один раз (в vector) и никогда не будет скопирован. Если вы передадите lvalue, аргумент будет скопирован ровно один раз (в vector).   -  person dyp    schedule 11.12.2013
comment
Я имею в виду перемещение std :: move в функцию / шаблон / любое тело. Это сделает его более читаемым. Но это кажется невозможным.   -  person tower120    schedule 11.12.2013


Ответы (2)


Насколько я понимаю, initializer_lists передают все по константной ссылке. move с одного, наверное, небезопасно. Конструктор initializer_list vector скопирует каждый из элементов.

Вот несколько ссылок: initializer_list и семантика перемещения

Нет, это не сработает так, как задумано; вы все равно получите копии. Я очень удивлен этим, так как я думал, что initializer_list существует для хранения массива временных файлов, пока они не будут перемещены.

begin и end для initializer_list return const T *, поэтому результатом перемещения в вашем коде будет T const && - неизменяемая ссылка на rvalue. От такого выражения невозможно избавиться. Он будет привязан к параметру функции типа T const &, потому что rvalue действительно привязывается к ссылкам const lvalue, и вы по-прежнему будете видеть семантику копирования.

Безопасно ли перемещать элементы список инициализаторов?

initializer_list предоставляет только константный доступ к своим элементам. Вы можете использовать const_cast для компиляции этого кода, но тогда ходы могут закончиться неопределенным поведением (если элементы initializer_list действительно const). Так что делать этот переезд небезопасно. Для этого есть обходные пути, если вам это действительно нужно.

Могу ли я инициализировать список-инициализировать вектор тип только для перемещения?

Синопсис 18.9 достаточно ясно показывает, что элементы списка инициализаторов всегда передаются через константную ссылку. К сожалению, в текущей версии языка не существует способа использования семантики перемещения в элементах списка инициализаторов.

вопросы, касающиеся дизайна std :: initializer_list

Из раздела 18.9 стандарта C ++:

Объект типа initializer_list обеспечивает доступ к массиву объектов типа const E. [Примечание: пара указателей или указатель плюс длина будут очевидными представлениями для initializer_list. initializer_list используется для реализации списков инициализаторов, как указано в 8.5.4. Копирование списка инициализаторов не копирует базовые элементы. - конец примечания]

Я думаю, что причина большинства этих вещей в том, что std :: initializer_list на самом деле не является контейнером. У него нет семантики значения, у него есть семантика указателя. Это становится очевидным из последней части цитаты: копирование списка инициализаторов не копирует базовые элементы. Поскольку они были предназначены исключительно для целей инициализации, я не думаю, что это так удивительно, что вы не получаете всех тонкостей более надежных контейнеров, таких как кортежи.

Если я правильно понимаю последнюю часть, это означает, что необходимы два набора копий, поскольку initializer_list не копирует базовые элементы. (Предыдущая цитата актуальна только в том случае, если вы пытаетесь использовать initializer_list без копирования элементы.)

Какова основная структура std :: initializer_list?

Нет, вы не можете перейти от элементов initializer_list, поскольку элементы initializer_list должны быть неизменными (см. Первое предложение абзаца, цитируемого выше). Это также причина того, почему только функции-члены с квалификацией const предоставляют вам доступ к элементам.


Если хотите, можете использовать emplace_back:

vec.emplace_back(TS());
vec.emplace_back(TS());
vec.push_back(std::move(ts1));
vec.push_back(std::move(ts2));
person Community    schedule 10.12.2013
comment
Я действительно могу использовать emplace_back. А вот с {} - выглядит очень красиво. - person tower120; 10.12.2013

Потому что здесь есть два элемента.

person Lightness Races in Orbit    schedule 10.12.2013
comment
Copy-ctor будет вызываться дважды для каждого аргумента, то есть всего четыре раза для двух аргументов. - person dyp; 10.12.2013
comment
Плохо сформулированный вопрос, тогда - person Lightness Races in Orbit; 10.12.2013
comment
OP даже говорит оператор присваивания копии;) - person dyp; 10.12.2013