Перемещение временных объектов в вектор - PullRequest
8 голосов
/ 06 октября 2011
#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( const 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( std::move( A() ) );
}

int main()
{
    vec v;

    foo( v );
    foo( v );
}

Приведенный выше пример производит следующий вывод:

constructor 1
move 1
destructor 1
constructor 2
move 2
move 1
destructor 1
destructor 2
destructor 1
destructor 2

Вопросы:

  1. Почему выполняется 1-й деструктор (но он не выполняется для 2-го объекта)?
  2. Почему перемещение 2-го объекта выполняется перед перемещением 1-го объекта?
  3. Почему в конце выполняются два деструктора для каждого объекта?

PS Я только что проверил, и объекты действительно размещены, как и ожидалось (1-й переходит в позицию 0 в векторе, а 2-й переходит в позицию 1 в векторе)

PPS Если это имеет значение, я использую gcc 4.3 и компилирую программу следующим образом:

g++ n1.cpp -Wall -Wextra -pedantic -ansi -std=c++0x -O3

Ответы [ 4 ]

9 голосов
/ 06 октября 2011

Я слегка перекодировал ваш пример:

#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";
}
  1. Я удалил const из конструктора перемещения.
  2. Я удалил std::move из push_back (это лишнее).
  3. Я вставил маркеры между вызовами на 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. Почему выполняется 1-й деструктор (но он не выполняется для 2-й объект)?

2-й деструктор выполняется для 2-го объекта в строке, помеченной // 1. Это уничтожение временного A() в конце второго вызова push_back.

  1. Почему перемещение 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, представляет собой уничтожение первого элемента из старого буфера после его перемещения в новый буфер.

  1. Почему в конце выполняются два деструктора для каждого объекта?

Это означает разрушение vector и, следовательно, уничтожение каждого из vector элементов.

5 голосов
/ 06 октября 2011

Разумное использование reserve решает половину вашей проблемы: http://ideone.com/5Lya6, уменьшая количество неожиданных ходов (которые вы явно не запрашиваете)

Также не забывайтечто деструктор темп все еще будет стрелять после того, как будет перемещен в вектор.Вот почему вы должны убедиться, что температура остается в нормальном , разрушаемом состоянии даже после назначения / строительства перемещения.

#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( const 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( std::move( A() ) );
}

int main()
{
    vec v;
    v.reserve(2);

    foo( v );
    foo( v );
}
4 голосов
/ 06 октября 2011

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

2 голосов
/ 06 октября 2011

Конструктор перемещения не "уничтожает" перемещенный объект.

#include <iostream>

struct Foo { 
  int i;
  bool active;

  Foo(int i): i(i), active(true) {}
  Foo(Foo&& rhs): i(rhs.i), active(rhs.active) { rhs.active = false; }
  Foo(Foo const& rhs): i(rhs.i), active(rhs.active) {}
  ~Foo() { std::cout << i << (active ? " active": " inactive") << "\n"; }
};


int main() {
  Foo foo;
  Bar bar(std::move(foo));
}

Выход дает:

1 active
1 inactive
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...