【问题标题】:How to avoid temporary copies when using emplace to add to a std::map?使用 emplace 添加到 std::map 时如何避免临时副本?
【发布时间】:2017-08-03 00:01:40
【问题描述】:
#include <iostream>
#include <map>
using namespace std;

struct FooStruct 
{
    int a;
    int b;
};

int main()
{
    map<int, FooStruct> fooMap;
    fooMap.emplace<int, FooStruct>(0, {1, 2});
    return 0;
}

在防止临时复制方面,以上是emplace的正确用法吗?上面的形式是不是比

fooMap.emplace(make_pair<int, FooStruct>(0, {1, 2}));

或者这些形式是否等效并且它们都避免创建FooStruct的临时副本?

【问题讨论】:

  • 区别是make_pair使用pair的移动构造函数语义,而其他方式调用模板的构造函数。
  • fooMap.emplace(std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(1, 2)); 呢?
  • @max66:你是说你的表格比上面提到的两个好?如果是这样,你能解释一下为什么会这样吗?
  • 否;我只向您展示另一种方法来做同样的事情;如果我没记错的话,它(大致)与第一种形式相同,但不需要明确的模板类型(在某些情况下可能很好)。
  • @max66: +1 消除了指定模板类型的需要。

标签: c++11 stl containers emplace


【解决方案1】:

如果您将“正确性”定义为简洁,您可能希望像这样使用std::map::insert 而不是std::map::emplace

fooMap.insert({0, {1, 2}});

对于emplace,您必须像在您的示例中那样明确指定类型,或者在@max66 建议的情况下明确定义FooStruct 中的构造函数:

fooMap.emplace(std::piecewise_construct,
    std::forward_as_tuple(0),
    std::forward_as_tuple(1, 2));

(也不够简洁)。

fooMap.insert({0, {1, 2}}); 不应不同于

fooMap.emplace(make_pair<int, FooStruct>(0, {1, 2}));

就创建的对象数量而言,正如@Swift 指出的那样,它还使用了std::pair 的移动构造函数。

如果“正确”意味着“可编译并且在运行时按预期工作”,那么您的两个示例都是正确的。

【讨论】:

  • 好的,谢谢。我真正追求的是直接在地图内存中构造的 FooStruct 的单个实例。 fooMap.emplace(std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(1, 2)); 做到了这一点。其他形式,无论是否简洁,都涉及创建一个临时 FooStruct 实例,这是我想避免的。
  • 您能否更新您的问题以反映您正在寻找最少数量的构造对象?此外,这种形式的emplace 可能会产生很少或根本没有运行时性能优势,因为编译器可以优化所有差异,您可能需要比较程序集。
【解决方案2】:

编辑:

在此线程中讨论的三种形式中,避免不必要复制的一种是@max66 提出的形式。以下代码及其输出捕获了这三种形式的实际操作

#include <iostream>
#include <map>
using namespace std;

struct FooStruct 
{
    FooStruct() 
    { 
        cout << "FooStruct Default Constructor" << endl; 
    }
    FooStruct(const FooStruct& other) 
    {
        this->a = other.a;
        this->b = other.b;
        cout << "FooStruct Copy Constructor" << endl;
    }
    FooStruct(int a, int b)
    {
        this->a = a;
        this->b = b;
        cout << "FooStruct Parametrized Constructor" << endl;
    }
    int a;
    int b;
};

输出:

foo.emplace<int, FooStruct>(0, {1, 2})
FooStruct Parametrized Constructor
FooStruct Copy Constructor
fooMap.emplace(make_pair<int, FooStruct>(1, { 2, 3 }))
FooStruct Parametrized Constructor
FooStruct Copy Constructor
FooStruct Copy Constructor
fooMap.emplace(std::piecewise_construct, std::forward_as_tuple(2), std::forward_as_tuple(2, 4))
FooStruct Parametrized Constructor

============

原始(错误)

我有点懒惰,在发布问题之前没有尝试深入挖掘。我现在看到所有这三种形式(第三种形式来自@max66 的评论)都是等价的,因为它们都避免创建FooStruct 的临时副本。

#include <iostream>
#include <map>
using namespace std;

struct FooStruct 
{
    FooStruct() { cout << "FooStruct Default Constructor" << endl; }
    FooStruct(int a, int b) { this->a = a; this->b = b; cout << "FooStruct Parametrized Constructor" << endl; }
    int a;
    int b;
};

int main()
{
    map<int, FooStruct> fooMap;
    fooMap.emplace<int, FooStruct>(0, {1, 2});
    fooMap.emplace(make_pair<int, FooStruct>(1, { 2, 3 }));
    fooMap.emplace(std::piecewise_construct, std::forward_as_tuple(2), std::forward_as_tuple(2, 4));
    return 0;
}

上述代码(使用 Visual C++ 2015 构建)产生以下输出:

FooStruct Parametrized Constructor
FooStruct Parametrized Constructor
FooStruct Parametrized Constructor

PS:我确实验证了上面输出中的每一行都对应于上面的单个 emplace 调用

【讨论】:

  • 我不同意“所有这三种形式 [...] 都是等价的,因为它们都避免了创建 FooStruct 的临时副本”。第一种和第三种形式都是如此;在第二种形式的情况下,据我所知,创建了对 int/FooStruct 的临时副本,用于使用复制构造函数构造映射中的对(移动构造函数,从 C ++11).
  • @max66:你是对的。我忘记了复制构造函数,显然得出了错误的结论。所以,现在我们知道涉及 forward_as_tuple 的表单是最好的解决方案。我将编辑我的答案以反映这一发现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-12
  • 2021-10-02
相关资源
最近更新 更多