push_back эффективнее emplace_back? - PullRequest
5 голосов
/ 26 мая 2020

Я хотел увидеть разницу между push_back и emplace_back, так как в нескольких местах я читал рекомендации, так как теперь лучше использовать emplace_back, поскольку «он может делать все, что может push_back, и даже больше», поэтому я ожидаю, что ti будет более эффективным. Но, к моему удивлению,

#include <iostream>     
#include <vector>    

class A
{
    public:
    A() {std::cout << "A const" << std::endl;}
    ~A() {std::cout << "A dest" << std::endl;}
    A(const A& a) {std::cout << "A copy const" << std::endl;}
    A(A&& a) {std::cout << "A move const" << std::endl;}
    A& operator=(const A& a) {std::cout << "A copy operator=" << std::endl; return *this; }
    A& operator=(A&& a) {std::cout << "A move operator=" << std::endl;  return *this; }
};

int main () {
    std::vector<A> va;
    std::cout <<"push:" << std::endl;
    va.push_back(A());
    std::cout <<std::endl<< "emplace:" << std::endl;
    va.emplace_back(A());

    std::cout <<std::endl<< "end:" << std::endl;

    return 0;
}

Результат:

push:
A const
A move const
A dest

emplace:
A const
A move const
A copy const
A dest
A dest

end:
A dest
A dest

emplace_back вызывает конструктор перемещения, а затем копирует его, когда push_back вызывает только одну константу перемещения. Я проверил с g ++ (Ubuntu 7.4.0-1ubuntu1 ~ 16.04 ~ ppa1) 7.4.0 и онлайн-оболочкой C ++ . Я что-то упускаю?

1 Ответ

10 голосов
/ 26 мая 2020

push_back не более эффективен, и наблюдаемые вами результаты обусловлены изменением размеров самого вектора.

Когда вы вызываете emplace после push_back, вектор имеет изменить размер, чтобы освободить место для второго элемента. Это означает, что он должен переместить A, который изначально находился внутри вектора, делая emplace более сложным.

Если вы заранее зарезервируете достаточно места в векторе, этого не произойдет. Обратите внимание на вызов va.reserve(2) после создания va:

#include <iostream>     
#include <vector>    

class A
{
    public:
    A() {std::cout << "A const" << std::endl;}
    ~A() {std::cout << "A dest" << std::endl;}
    A(const A& a) {std::cout << "A copy const" << std::endl;}
    A(A&& a) {std::cout << "A move const" << std::endl;}
    A& operator=(const A& a) {std::cout << "A copy operator=" << std::endl; return *this; }
    A& operator=(A&& a) {std::cout << "A move operator=" << std::endl;  return *this; }
};

int main () {
    std::vector<A> va;
    // Now there's enough room for two elements
    va.reserve(2);
    std::cout <<"push:" << std::endl;
    va.push_back(A());
    std::cout <<std::endl<< "emplace:" << std::endl;
    va.emplace_back(A());

    std::cout <<std::endl<< "end:" << std::endl;

    return 0;
}

Соответствующий вывод:

push:
A const
A move const
A dest

emplace:
A const
A move const
A dest

end:
A dest
A dest

Можем ли мы сделать вещи еще более эффективными? Да! emplace_back принимает любые аргументы, которые вы ему предоставили, и перенаправляет их в конструктор A. Поскольку A имеет конструктор, который не принимает аргументов, вы также можете использовать emplace_back без аргументов! Другими словами, мы меняем

va.emplace_back(A());

на

va.emplace_back(); // No arguments necessary since A is default-constructed

Это не приводит ни к копированию, ни к перемещению:

push:
A const
A move const
A dest

emplace:
A const

end:
A dest
A dest

Примечание по изменение размеров векторов: Важно отметить, что реализация std::vector умна. Если бы A был тривиально копируемым типом, std::vector мог бы изменить размер на месте без дополнительного копирования с помощью системной функции, подобной realloc. Однако, поскольку конструкторы и разрушения A содержат код, realloc здесь использовать нельзя.

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