12

push_back против emplace_back: в чем разница?

21

Я немного запутался в различии между методами <code>push_back</code> и <code>emplace_back</code> в стандартной библиотеке C++.

Вот определения этих методов:

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

Учитывая, что существует перегрузка метода <code>push_back</code>, принимающая rvalue-ссылку, я не совсем понимаю, в чем заключается основное назначение метода <code>emplace_back</code>. В каких случаях стоит предпочесть использовать <code>emplace_back</code>, а не <code>push_back</code>?

5 ответ(ов)

2

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

template <class... Args> void emplace_back(Args&&... args);

Возможно передать value_type, который будет передан в конструктор копирования. Поскольку emplace_back перенаправляет аргументы, это означает, что если у вас нет rvalue, контейнер все равно будет хранить "скопированную" копию, а не перемещенную.

std::vector<std::string> vec;
vec.emplace_back(std::string("Hello")); // перемещение
std::string s;
vec.emplace_back(s); // копирование

Однако вышеуказанное должно быть идентично тому, что делает push_back. Функция emplace_back скорее предназначена для таких случаев, как:

std::vector<std::pair<std::string, std::string>> vec;
vec.emplace_back(std::string("Hello"), std::string("world")); 
// в этом случае будет вызван такой конструктор:
// template<class U, class V> pair(U&& x, V&& y);
// без создания ненужных копий строк
1

Оптимизация для emplace_back может быть продемонстрирована на следующем примере.

При использовании emplace_back будет вызван конструктор A (int x_arg).

В случае push_back сначала вызывается A (int x_arg), а затем вызывается конструктор перемещения A (A &&rhs).

Конечно, конструктор должен быть помечен как explicit, но в текущем примере было бы уместно убрать это ограничение.

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

Результат выполнения:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)

Таким образом, при использовании emplace_back происходит меньшая нагрузка на производительность, так как не требуется дополнительный вызов конструктора перемещения, в то время как push_back требует создания объекта и последующего его перемещения.

0

Пример использования списков:

// Конструирует элементы на месте.
emplace_back("element");

// Создает новый объект, а затем копирует (или перемещает) этот объект.
push_back(ExplicitDataType{"element"});

Метод emplace_back позволяет создать объект прямо в контейнере, что может быть эффективнее, особенно для сложных типов. Метод push_back же ожидает, что объект уже создан, и он просто будет скопирован или перемещен в контейнер.

0

В качестве хорошего примера использования методов push_back и emplace_back можно ознакомиться с документацией на cppreference: emplace_back.

Обратите внимание, что при использовании метода push_back происходит перемещение объекта (move operation), а при использовании emplace_back этого не происходит, поскольку данный метод создает объект непосредственно в контейнере, что может быть более эффективным в некоторых случаях. Это позволяет избежать лишнего копирования, так как объект конструируется на месте.

0

emplace_back в стандартной библиотеке действительно будет пересылать аргументы конструктору vector<Object>::value_type при добавлении элементов в вектор. Насколько мне известно, в Visual Studio изначально не поддерживались вариативные шаблоны, но с выходом Visual Studio 2013 RC они были добавлены, что предполагает, что корректная сигнатура функции emplace_back также может быть реализована.

При использовании emplace_back, если вы передаете аргументы напрямую в конструктор vector<Object>::value_type, то, строго говоря, тип элементов не обязательно должен быть перемещаемым или копируемым. В случае с vector<NonCopyableNonMovableObject> этот подход не имеет практического смысла, поскольку для увеличения размера вектора требуется, чтобы хранимый тип был копируемым или перемещаемым.

Тем не менее, обратите внимание, что это может быть полезно для std::map<Key, NonCopyableNonMovableObject>, поскольку после выделения записи в карте элементы не требуют перемещения или копирования, в отличие от вектора. Это значит, что вы можете эффективно использовать std::map с типами, которые не поддерживают копирование или перемещение.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь