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

Почему вектор:: push_back и emplace_back вызывают метод value_type:: constructor дважды?

У меня есть этот класс:

class Foo {
public:
    Foo() {}
    Foo(const Foo&){cout << "constructed by lvalue reference." <<endl; }
    Foo(Foo&& ) {cout << "constructed by rvalue reference." << endl; }
};

тогда я вставляю в вектор:

Foo foo{};
vf.push_back(foo);

Выход удивляет:

constructed by lvalue reference.
constructed by lvalue reference.

Я предполагаю, что он был скопирован при передаче параметров, поэтому я попробовал:

vf.push_back(move(foo));

и

vf.push_back(forward<Foo>(foo));

Вывод немного отличается из-за семантики перемещения, но по-прежнему вызывает конструктор дважды:

constructed by rvalue reference.
constructed by lvalue reference.

Почему конструкторы дважды вызывались? Насколько это влияет на производительность? Как я могу избежать этого?


Я использую mingw-gcc-4.7.1 в Windows Vista​​p >

Общий пример:

#include <iostream>
#include <vector>

using namespace std;

class Foo {
public:
    Foo() {}
    Foo(const Foo&){cout << "constructed by lvalue reference." <<endl; }
    Foo(Foo&& ) {cout << "constructed by rvalue reference." << endl; }
};


int main(int argc, char **argv, char** envp)
{
    vector<Foo> vf;
    cout << "Insert a temporary." << endl;
    vf.emplace_back(Foo{});

    Foo foo{};
    cout << "Insert a variable." << endl;
    vf.emplace_back(foo);

    return 0;
}

Точный выход:

Insert a temporary.
constructed by rvalue reference.
Insert a variable.
constructed by lvalue reference.
constructed by lvalue reference.
4b9b3361

Ответ 1

Когда вы вставляете новые элементы в вектор, вектор может выделять больше памяти для соответствия этим объектам. Когда это произойдет, ему нужно скопировать все его элементы в новое место памяти. Это вызовет конструктор копирования. Поэтому, когда вы вставляете элемент, вы получаете конструктор для этого нового элемента и конструктора при копировании предыдущего элемента.

Ответ 2

  vector<Foo> vf;
  cout << "Insert a temporary." << endl;
  vf.emplace_back(Foo{});

Что происходит выше, так это создание временного Foo.

Это временное значение затем используется для построения Foo внутри vector. Так что "построенный по ссылке rvalue" - это то, о чем вы просили.

Если вы хотите просто построить Foo на месте, попробуйте:

  vs.emplace_back();

Далее:

  Foo foo{};
  cout << "Insert a variable." << endl;
  vf.emplace_back(foo);

здесь вы создаете не временную Foo. Затем вы инструктируете std::vector построить новый элемент в конце списка.

Интересно, что вы получаете две конструкции по ссылке lvalue. Второй, по-видимому, вызван изменением размера. Почему изменение размера приводит к тому, что вы ссылаетесь на ссылку lvalue вместо ссылки rvalue - это трюк: если ваш конструктор move не помечен noexcept, std::vector возвращается к копии вместо move!

Здесь - живой пример, иллюстрирующий вышеприведенные принципы:

#include <iostream>
#include <vector>

using namespace std;

class Foo {

public:
  Foo() {}
  virtual ~Foo() {}
  Foo(const Foo&){cout << "constructed by lvalue reference." <<endl; }
  Foo(Foo&){cout << "constructed by non-const lvalue reference." <<endl; }
  Foo(Foo&& ) noexcept {cout << "constructed by rvalue reference." << endl; }
};


int main(int argc, char **argv, char** envp)
{
  vector<Foo> vf;
  cout << "Insert a temporary.  One move:" << endl;
  vf.emplace_back(Foo{});
  cout << "Insert a temporary(2).  Two moves:" << endl;
  vf.emplace_back(Foo{});
  cout << "Resize with temporary(3).  Two moves:" << endl;
  vf.resize(10);

  vector<Foo> vf2;
  Foo foo{};
  cout << "Insert a variable.  One copy:" << endl;
  vf2.emplace_back(foo);
  cout << "Insert a variable(2).  One move, one copy:" << endl;
  vf2.emplace_back(foo);
  cout << "Resize with variable(3).  Two moves:" << endl;
  vf2.resize(10);

  vector<Foo> vf3;
  cout << "Insert a nothing.  No copy or move:" << endl;
  vf3.emplace_back();
  cout << "Insert a nothing(2).  One move:" << endl;
  vf3.emplace_back();
  cout << "Resize with nothing(3).  Two moves:" << endl;
  vf3.resize(10);
}

Ответ 3

Общая реализация std::vector::push_back выглядит так:

void push_back(value_type _Val)
{   // insert element at end
    insert_n(size(), 1, _Val);
}

Как вы можете видеть, входной параметр передается по значению (поэтому он копируется) как в объявлении push_back, так и в объявлении insert_n. Следовательно, copy-constructor вызывается дважды.

После очистки вашего синтаксиса:

#include <iostream>
#include <vector>

using namespace std;

class Foo 
{
public:
    Foo() {}
    Foo(const Foo&) {cout << "constructed by lvalue reference." <<endl; }
    Foo(Foo&&) {cout << "constructed by rvalue reference." << endl; }
};


int main()
{
    vector<Foo> vf;
    cout << "Size = " << vf.size() << endl;
    cout << "Capacity = " << vf.capacity() << endl;

    cout << "Insert a temporary" << endl;
    vf.push_back(Foo()); // this is still very discouraged syntax

    Foo foo;
    cout << "Insert a variable." << endl;
    vf.push_back(foo);

    return 0;
}

Вы получаете следующий результат:

Size = 0
Capacity = 0
Insert a temporary
constructed by rvalue reference.
Insert a variable.
constructed by rvalue reference.
constructed by lvalue reference.
Press any key to continue . . .

В этом примере я использую стандартную версию std::vector (которая передается по const-ссылке или по ссылке на ссылку). Первоначальный вызов push_back создает емкость 1 (и размер 1). Второй вызов создает новый блок памяти, перемещает первый элемент и копирует второй (вновь добавленный) элемент.

Что касается производительности, вы не собираетесь сильно пострадать от небольших перераспределений. Существует несколько различных моделей общей памяти (один с Visual Studio растет по экспоненте каждый раз, когда он должен расти, чтобы уменьшить потребность в нем в будущем). Если вы знаете, что вы начнете с 100 элементов, вы должны зарезервировать место при создании своего вектора, поэтому распределение происходит только один раз, что также предотвратит необходимость перемещения существующих элементов при вставке новых элементов (из-за того, что вы не будете будет превышать вашу способность несколько раз).