如何在 `push_*()` 和 `emplace_*()` 函数之间进行选择?

How to choose between `push_*()` and `emplace_*()` functions?

我了解两个函数变体之间的区别。

我的问题是:我通常应该使用良好的旧 push_*() 版本,并且只有在我的分析器告诉我这将有利于性能(即不要过早优化)时才切换到 emplace_*() 吗?或者我应该切换到使用 emplace_*() 作为默认值(也许不要不必要地悲观代码 - 类似于 i++for 循环中的 ++i)?

在现实的非人为用例中,是否有任何变体比另一个变体更通用(即,对插入的类型施加更少的限制)?

在编写代码时,我不会担心性能问题。当您已经拥有可以分析的代码时,性能将在以后使用。

我宁愿担心代码的表现力。粗略地说,push_back 适用于当您有一个元素并想在容器中放置一个副本时。 emplace_back就是原地构造元素。

考虑下"wtf-count":

struct foo {int x;int y;};

void foo_add(const foo& f,std::vector<foo>& v) {
    v.emplace_back(f);   // wtf ?!? we already have a foo
    v.push_back(f);      // ... simply make a copy (or move)
}

void foo_add(int x, int y, std::vector<foo>& v) {
    auto z = foo{x,y};      // wtf ?!? 
    f.push_back(z);         // why create a temporary?
    f.emplace_back(x,y);    // ... simply construct it in place
} 

如果你天真地从push_back切换到emplace_back,你将毫无优势可言。考虑以下代码:

#include <iostream>
#include <string>
#include <vector>

struct President
{
    std::string name;
    std::string country;
    int year;

    President(std::string p_name, std::string p_country, int p_year) :
            name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }
    President(President&& other) :
            name(std::move(other.name)), country(std::move(other.country)),
            year(other.year)
    {
        std::cout << "I am being moved.\n";
    }
    President& operator=(const President& other) = default;
};

int main()
{
    std::vector<President> elections;
    std::cout << "emplace_back:\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);

    std::vector<President> reElections;
    std::cout << "\npush_back:\n";
    reElections.push_back(
            President("Franklin Delano Roosevelt", "the USA", 1936));

    std::cout << "\nContents:\n";

    for (President const& president : elections)
    {
        std::cout << president.name << " was elected president of "
                            << president.country << " in " << president.year << ".\n";
    }

    for (President const& president : reElections)
    {
        std::cout << president.name << " was re-elected president of "
                            << president.country << " in " << president.year << ".\n";
    }
}

如果您将 push_back 替换为 emplace_back,您仍然有一个构造,然后是一个移动。仅当您传递构造所需的参数而不是构造的实例本身(参见对 emplace_back 的调用)时,您才省力。

emplace 函数是委托构造函数。

假设您有一个 T 容器。

如果你已经一个T,可能是常量,可能是右值,可能是none;
然后你使用 push_xxx().
您的对象将 copied/moved 放入容器中。

如果你想构造一个T,那么你使用emplace_xxx()与发送构造函数[=29]相同的参数=].
将直接在容器中构造一个对象。

Emplace 函数比 push 函数更通用。在任何情况下,它们都不会降低效率,相反 - 它们可以更高效,因为当您需要从参数构造容器元素时,它们允许优化容器元素的一个 copy/move 操作。无论如何,将元素放入容器时涉及 copy/move,emplace 和 push 操作是等价的。

如果您确实想在 copying/moving 将元素放入容器之前强制执行构造,则推送可能更可取。例如,如果您的元素类型在其构造函数中有一些您希望在修改容器之前执行的特殊逻辑。不过这种情况很少见。