Подтвердить что ты не робот

Почему вместе копируются и перемещаются конструкторы?

Рассмотрим следующий код:

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

class A
{
public:
     A(int) { cout << "int" << endl; }
     A(A&&) { cout << "move" << endl; }
     A(const A&) { cout << "copy" << endl; }
};

int main()
{
    vector<A> v
    {
        A(10), A(20), A(30)
    };

    _getch();
    return 0;
}

Вывод:

int
int
int
copy
copy
copy

A(10), A(20) и A(30) являются временными, верно?

Итак, почему вызван конструктор копирования? Не следует ли вместо этого вызывать конструктор перемещения?

Передача move(A(10)), move(A(20)), move(A(30)) вместо этого:

int
move
int
move
int
move
copy
copy
copy

В этом случае вызывается либо копировать, либо перемещать конструктор.

Что происходит?

4b9b3361

Ответ 1

std::vector можно построить из std::initializer_list, и вы вызываете этот конструктор. Правила для конструкции initializer_list утверждают, что этот конструктор агрессивно предпочтителен:

Конструктор - это конструктор списка инициализаторов, если его первый параметр имеет тип std::initializer_list<E>или ссылку на возможно cv-квалифицированный std::initializer_list<E> для некоторого типа E, и либо есть нет других параметров, иначе все остальные параметры имеют аргументы по умолчанию (8.3.6). [Примечание: список инициализаторов конструкторы предпочтительнее других конструкторов в инициализации списка <... > ]

Кроме того, из-за какой-то странной реализации initializer_list в качестве массива, выделенного под капотом, элементы соответствующего массива, на которые ссылается std::initializer_list<E>, принудительно инициализируются копией (которые могут быть удалены):

Объект типа std::initializer_list<E> строится из списка инициализаторов, как если бы реализация выделен массив элементов N типа E, где N - количество элементов в списке инициализаторов. Каждый элемент этого массива инициализируется копией с соответствующим элементом списка инициализаторов и объект std::initializer_list<E> создается для обращения к этому массиву

(Обе приведенные выше ссылки из N3337 [dcl.init.list])

Однако в вашем первом примере копии могут/будут удалены, несмотря на имя ([dcl.init]/14), чтобы вы не увидели дополнительную конструкцию копии (их также можно переместить). Вы можете поблагодарить свой компилятор за что, поскольку копирование не требуется в С++ 11 (хотя оно находится на С++ 17).

Для получения дополнительной информации см. [class.copy] ("Когда определенные критерии выполнены, реализации разрешено опускать конструкцию копирования/перемещения класса объект... ").

Заключительная часть - ключ:

[support.initlist] указывает, что

Объект типа initializer_list<E> предоставляет доступ к массиву объектов типа const E.

Это означает, что std::vector не может напрямую захватить память; он должен быть скопирован, вот где вы в конечном итоге видите, что вызываемые копии создаются.

Во втором примере, как указано в заявлении Kerrek SB, вы предотвратили упомянутое ранее копирование, и вызвали дополнительные накладные расходы.

Ответ 2

A (10), A (20), A (30) являются временными, верно?

Правильно.

Итак, почему вызван конструктор копирования? Не следует ли вместо этого вызывать конструктор перемещения?

К сожалению, невозможно перейти от std::initializer_list, что и использует этот конструктор std::vector.

Проходящий ход (A (10)), переместите (A (20)), переместите (A (30)) вместо

В этом случае вызывается либо копирование, либо перемещение конструктора. Что происходит?

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