Я слегка перекодировал ваш пример:
#include <iostream>
#include <utility>
#include <vector>
int i = 0;
struct A
{
A() : j( ++i )
{
std::cout<<"constructor "<<j<<std::endl;
}
A( const A & c) : j(c.j)
{
std::cout<<"copy "<<j<<std::endl;
}
A( A && c) : j(c.j)
{
std::cout<<"move "<<j<<std::endl;
}
~A()
{
std::cout<<"destructor "<<j<<std::endl;
}
int j;
};
typedef std::vector< A > vec;
void foo( vec & v )
{
v.push_back( A() );
}
int main()
{
vec v;
std::cout << "A\n";
foo( v );
std::cout << "B\n";
foo( v );
std::cout << "C\n";
}
- Я удалил
const
из конструктора перемещения.
- Я удалил
std::move
из push_back
(это лишнее).
- Я вставил маркеры между вызовами на
foo
.
Для меня это выводит похожий на ваш код:
A
constructor 1
move 1
destructor 1
B
constructor 2
move 2
copy 1
destructor 1
destructor 2 // 1
C
destructor 2
destructor 1
- Почему выполняется 1-й деструктор (но он не выполняется для
2-й объект)?
2-й деструктор выполняется для 2-го объекта в строке, помеченной // 1
. Это уничтожение временного A()
в конце второго вызова push_back
.
- Почему перемещение 2-го объекта выполняется перед перемещением 1-го
объект
Примечание: для меня 1-й объект скопирован, а не перемещен, подробнее об этом ниже.
Ответ: Исключительная безопасность.
Объяснение: В течение этого push_back
вектор обнаруживает, что у него есть полный буфер (один), и ему нужно создать новый буфер с комнатой для двоих. Это создает новый буфер. Затем перемещает второй объект в этот буфер (в конце его). Если эта конструкция выдает исключение, исходный буфер все еще не поврежден, а vector
остается неизменным. В противном случае элементы перемещаются или копируются из первого буфера во второй буфер (таким образом, перемещается / копируется первый элемент вторым).
Если A
имеет конструктор перемещения noexcept
, move
будет использоваться для его перемещения из старого буфера в новый. Однако, если конструктор перемещения не noexcept
, тогда будет использоваться copy
. Это опять для исключительной безопасности. Если перемещение из старого буфера в новый может потерпеть неудачу, то старый буфер должен быть оставлен без изменений, чтобы vector
мог быть восстановлен в своем первоначальном состоянии.
Если я добавлю noexcept
в ваш конструктор ходов:
A( A && c) noexcept : j(c.j)
{
std::cout<<"move "<<j<<std::endl;
}
Тогда мой вывод:
A
constructor 1
move 1
destructor 1
B
constructor 2
move 2
move 1
destructor 1 // 2
destructor 2
C
destructor 2
destructor 1
Обратите внимание, что строка, помеченная // 2
, представляет собой уничтожение первого элемента из старого буфера после его перемещения в новый буфер.
- Почему в конце выполняются два деструктора для каждого объекта?
Это означает разрушение vector
и, следовательно, уничтожение каждого из vector
элементов.